diff --git a/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java b/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java index a8d2509a..addfd30f 100644 --- a/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java +++ b/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java @@ -31,6 +31,11 @@ public class ClientTerrainCache { */ Map cacheMapHalfRes = new ConcurrentHashMap(); + /** + * The map of half res chunk key -> chunk data + */ + Map cacheMapQuarterRes = new ConcurrentHashMap(); + /** * The list of keys in the cache */ @@ -146,6 +151,9 @@ public class ClientTerrainCache { case 1: { return cacheMapHalfRes; } + case 2: { + return cacheMapQuarterRes; + } default: { throw new Error("Invalid stride probided! " + stride); } diff --git a/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java b/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java index 311ab8a5..e0b00e96 100644 --- a/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java +++ b/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java @@ -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,11 @@ 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 octree holding all the chunks to evaluate */ @@ -164,7 +174,8 @@ public class ClientDrawCellManager { } else if(shouldRequest(playerPos, node)){ Globals.profiler.beginCpuSample("ClientDrawCellManager.request"); DrawCell cell = node.getData(); - this.requestChunks(node); + List highResFaces = this.solveHighResFace(node); + this.requestChunks(node, highResFaces); cell.setHasRequested(true); Globals.profiler.endCpuSample(); updated = true; @@ -174,6 +185,11 @@ public class ClientDrawCellManager { List highResFaces = this.solveHighResFace(node); if(containsDataToGenerate(node,highResFaces)){ node.getData().generateDrawableEntity(textureAtlas, lodLevel, highResFaces); + } 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; @@ -215,6 +231,11 @@ public class ClientDrawCellManager { node.isLeaf() && node.canSplit() && ( + ( + node.getLevel() < this.chunkTree.getMaxLevel() - 2 && + this.getMinDistance(pos, node) <= QUARTER_RES_DIST + ) + || ( node.getLevel() < this.chunkTree.getMaxLevel() - 1 && this.getMinDistance(pos, node) <= HALF_RES_DIST @@ -358,10 +379,15 @@ public class ClientDrawCellManager { node.getLevel() == this.chunkTree.getMaxLevel() - 1 && this.getMinDistance(pos, node) > FULL_RES_DIST ) - || + || ( + node.getLevel() == this.chunkTree.getMaxLevel() - 2 && this.getMinDistance(pos, node) > HALF_RES_DIST ) + || + ( + this.getMinDistance(pos, node) > QUARTER_RES_DIST + ) ) ; } @@ -389,6 +415,12 @@ public class ClientDrawCellManager { && this.getMinDistance(pos, node) <= HALF_RES_DIST ) + || + ( + node.getLevel() == this.chunkTree.getMaxLevel() - 2 + && + this.getMinDistance(pos, node) <= QUARTER_RES_DIST + ) ) ; } @@ -410,12 +442,18 @@ public class ClientDrawCellManager { // && // this.getMinDistance(pos, node) <= FULL_RES_DIST ) - || + || ( node.getLevel() == this.chunkTree.getMaxLevel() - 1 && this.getMinDistance(pos, node) <= HALF_RES_DIST ) + || + ( + node.getLevel() == this.chunkTree.getMaxLevel() - 2 + && + this.getMinDistance(pos, node) <= QUARTER_RES_DIST + ) ) ; } @@ -494,7 +532,7 @@ public class ClientDrawCellManager { * Requests all chunks for a given draw cell * @param cell The cell */ - private void requestChunks(WorldOctTree.FloatingChunkTreeNode node){ + private void requestChunks(WorldOctTree.FloatingChunkTreeNode node, List highResFaces){ DrawCell cell = node.getData(); int lod = this.chunkTree.getMaxLevel() - node.getLevel(); int spacingFactor = (int)Math.pow(2,lod); @@ -518,6 +556,51 @@ public class ClientDrawCellManager { } } } + 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); + Globals.clientTerrainManager.requestChunk(posToCheck.x, posToCheck.y, posToCheck.z, highResLod); + } + } + } + } + } } /** diff --git a/src/main/java/electrosphere/client/terrain/cells/DrawCell.java b/src/main/java/electrosphere/client/terrain/cells/DrawCell.java index 9e09ccbc..239c04d8 100644 --- a/src/main/java/electrosphere/client/terrain/cells/DrawCell.java +++ b/src/main/java/electrosphere/client/terrain/cells/DrawCell.java @@ -14,7 +14,6 @@ import electrosphere.entity.Entity; import electrosphere.entity.types.terrain.TerrainChunk; import electrosphere.renderer.meshgen.TransvoxelModelGeneration; import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData; -import electrosphere.server.terrain.manager.ServerTerrainChunk; /** * A single drawcell - contains an entity that has a physics mesh and potentially graphics @@ -88,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; /** @@ -118,6 +122,7 @@ public class DrawCell { Globals.profiler.endCpuSample(); if(!success){ this.setHasRequested(false); + this.failedGenerationAttempts++; return; } if(modelEntity != null){ @@ -131,6 +136,7 @@ public class DrawCell { Globals.profiler.endCpuSample(); if(!success){ this.setHasRequested(false); + this.failedGenerationAttempts++; return; } } @@ -464,6 +470,9 @@ public class DrawCell { */ public void setHasRequested(boolean hasRequested) { this.hasRequested = hasRequested; + if(!this.hasRequested){ + this.failedGenerationAttempts = 0; + } } /** @@ -490,6 +499,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){ + failedGenerationAttempts = failedGenerationAttempts + 1; + } + } diff --git a/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java b/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java index 188a5d37..6700cac7 100644 --- a/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java +++ b/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java @@ -36,6 +36,11 @@ public class ServerChunkCache { */ Map cacheMapHalfRes = new ConcurrentHashMap(); + /** + * The map of quarter res chunk key -> chunk data + */ + Map cacheMapQuarterRes = new ConcurrentHashMap(); + /** * Tracks how recently a chunk has been queries for (used for evicting old chunks from cache) */ @@ -192,6 +197,9 @@ public class ServerChunkCache { case 1: { return cacheMapHalfRes; } + case 2: { + return cacheMapQuarterRes; + } default: { throw new Error("Invalid stride probided! " + stride); }