Compare commits

..

No commits in common. "afabf5595e7f29c9e17e93340378caf61535cafb" and "4f639cc6beb074e4cf51d848736e27ac82d7fcaa" have entirely different histories.

28 changed files with 319 additions and 875 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,ChunkData.NO_STRIDE);
return Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldX,worldY,worldZ);
}

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.NO_STRIDE);
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPosition);
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,ChunkData.NO_STRIDE);
this.currentChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos);
// //evaluate top cells if chunk above this one exists
this.aboveChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(worldPos).add(0,1,0),ChunkData.NO_STRIDE);
this.aboveChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(worldPos).add(0,1,0));
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.NO_STRIDE);
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(this.getWorldPos());
if(data == null){
return false;
}

View File

@ -12,11 +12,6 @@ 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
@ -44,23 +39,16 @@ 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, int stride){
public ChunkData(int worldX, int worldY, int worldZ){
this.worldX = worldX;
this.worldY = worldY;
this.worldZ = worldZ;
this.stride = stride;
}
@ -223,13 +211,5 @@ 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,7 +5,6 @@ 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;
@ -17,50 +16,19 @@ import io.github.studiorailgun.HashUtils;
*/
public class ClientTerrainCache {
/**
* Cache capacity
*/
//cache capacity
int cacheSize;
/**
* 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
*/
//the map of chunk key -> chunk data
Map<Long,ChunkData> cacheMap = 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 lock on the terrain cache
* The map tracking chunks that have been requested
*/
Semaphore lock = new Semaphore(1);
Map<Long,Boolean> requestedChunks = new ConcurrentHashMap<Long,Boolean>();
/**
* Constructor
@ -78,27 +46,23 @@ 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){
lock.acquireUninterruptibly();
Map<Long,ChunkData> cache = this.getCache(chunkData.getStride());
cache.put(getKey(worldX,worldY,worldZ),chunkData);
cacheMap.put(getKey(worldX,worldY,worldZ),chunkData);
chunkPositionMap.put(chunkData,new Vector3i(worldX,worldY,worldZ));
while(cacheList.size() > cacheSize){
Long currentChunk = cacheList.remove(0);
cache.remove(currentChunk);
ChunkData data = cacheMap.remove(currentChunk);
Vector3i worldPos = data.getWorldPos();
requestedChunks.remove(getKey(worldPos.x,worldPos.y,worldPos.z));
}
lock.release();
}
/**
* Evicts all chunks from the cache
*/
public void evictAll(){
lock.acquireUninterruptibly();
this.cacheList.clear();
this.cacheMapFullRes.clear();
this.cacheMapHalfRes.clear();
this.cacheMap.clear();
this.chunkPositionMap.clear();
lock.release();
}
@ -118,14 +82,10 @@ 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, int stride){
lock.acquireUninterruptibly();
boolean rVal = this.getCache(stride).containsKey(getKey(worldX,worldY,worldZ));
lock.release();
return rVal;
public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
return cacheMap.containsKey(getKey(worldX,worldY,worldZ));
}
@ -137,14 +97,10 @@ 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, int stride){
lock.acquireUninterruptibly();
ChunkData rVal = this.getCache(stride).get(getKey(worldX,worldY,worldZ));
lock.release();
return rVal;
public ChunkData getSubChunkDataAtPoint(int worldX, int worldY, int worldZ){
return cacheMap.get(getKey(worldX,worldY,worldZ));
}
/**
@ -152,7 +108,7 @@ public class ClientTerrainCache {
* @return The list of all chunks in the cache
*/
public Collection<ChunkData> getAllChunks(){
return this.cacheMapFullRes.values();
return this.cacheMap.values();
}
/**
@ -163,33 +119,34 @@ public class ClientTerrainCache {
public Vector3i getChunkPosition(ChunkData chunk){
return chunkPositionMap.get(chunk);
}
/**
* Gets the cache
* @param stride The stride of the data
* @return The cache to use
* Gets the number of cells that have been requested
* @return The number of cells that have been requested
*/
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);
}
}
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);
}
}

View File

@ -25,11 +25,6 @@ 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
*/
@ -40,51 +35,6 @@ 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
*/
@ -130,11 +80,6 @@ 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
@ -155,24 +100,12 @@ public class ClientDrawCellManager {
Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
//the sets to iterate through
updatedLastFrame = true;
int attempts = 0;
validCellCount = 0;
//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);
while(updatedLastFrame && attempts < UPDATE_ATTEMPTS_PER_FRAME){
FloatingChunkTreeNode<DrawCell> rootNode = this.chunkTree.getRoot();
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos);
attempts++;
}
}
Globals.profiler.endCpuSample();
@ -182,10 +115,9 @@ 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, int minLeafLod){
private boolean recursivelyUpdateCells(FloatingChunkTreeNode<DrawCell> node, Vector3d playerPos){
Vector3d playerRealPos = EntityUtils.getPosition(Globals.playerEntity);
boolean updated = false;
if(this.shouldSplit(playerPos, node)){
@ -229,34 +161,19 @@ public class ClientDrawCellManager {
Globals.profiler.endCpuSample();
updated = true;
} else if(shouldRequest(playerPos, node, minLeafLod)){
} else if(shouldRequest(playerPos, node)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.request");
//calculate what to request
DrawCell cell = node.getData();
List<DrawCellFace> highResFaces = this.solveHighResFace(node);
//actually send requests
if(this.requestChunks(node, highResFaces)){
cell.setHasRequested(true);
}
this.requestChunks(node);
cell.setHasRequested(true);
Globals.profiler.endCpuSample();
updated = true;
} else if(shouldGenerate(playerPos, node, minLeafLod)){
} else if(shouldGenerate(playerPos, node)){
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;
@ -264,7 +181,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, minLeafLod);
boolean childUpdate = recursivelyUpdateCells(child, playerPos);
if(childUpdate == true){
updated = true;
}
@ -299,22 +216,7 @@ public class ClientDrawCellManager {
node.canSplit() &&
(
(
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 &&
node.getLevel() < this.chunkTree.getMaxLevel() - 1 &&
this.getMinDistance(pos, node) <= HALF_RES_DIST
)
||
@ -453,28 +355,13 @@ public class ClientDrawCellManager {
!node.isLeaf() &&
(
(
node.getLevel() == this.chunkTree.getMaxLevel() - HALF_RES_LOD &&
node.getLevel() == this.chunkTree.getMaxLevel() - 1 &&
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
)
)
;
}
@ -483,15 +370,13 @@ 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, int minLeafLod){
public boolean shouldRequest(Vector3d pos, FloatingChunkTreeNode<DrawCell> node){
return
node.isLeaf() &&
node.getData() != null &&
!node.getData().hasRequested() &&
(this.chunkTree.getMaxLevel() - node.getLevel()) < minLeafLod &&
(
(
node.getLevel() == this.chunkTree.getMaxLevel()
@ -500,27 +385,9 @@ public class ClientDrawCellManager {
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - HALF_RES_LOD
node.getLevel() == this.chunkTree.getMaxLevel() - 1
&&
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
this.getMinDistance(pos, node) <= HALF_RES_DIST
)
)
;
@ -530,44 +397,24 @@ 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, int minLeafLod){
public boolean shouldGenerate(Vector3d pos, FloatingChunkTreeNode<DrawCell> node){
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() - HALF_RES_LOD
node.getLevel() == this.chunkTree.getMaxLevel() - 1
&&
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
this.getMinDistance(pos, node) <= HALF_RES_DIST
)
)
;
@ -643,19 +490,31 @@ 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 boolean requestChunks(WorldOctTree.FloatingChunkTreeNode<DrawCell> node, List<DrawCellFace> highResFaces){
private void requestChunks(WorldOctTree.FloatingChunkTreeNode<DrawCell> node){
DrawCell cell = node.getData();
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);
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);
if(
posToCheck.x >= 0 &&
posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() &&
@ -663,65 +522,15 @@ 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, lod)
!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z)
){
//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);
if(!Globals.clientTerrainManager.requestChunk(posToCheck.x, posToCheck.y, posToCheck.z, lod)){
return false;
}
Globals.clientTerrainManager.requestChunk(posToCheck.x, posToCheck.y, posToCheck.z);
}
}
}
}
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;
}
/**
@ -732,12 +541,11 @@ public class ClientDrawCellManager {
*/
private boolean containsDataToGenerate(WorldOctTree.FloatingChunkTreeNode<DrawCell> node, List<DrawCellFace> highResFaces){
DrawCell cell = node.getData();
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);
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);
if(
posToCheck.x >= 0 &&
posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() &&
@ -745,40 +553,38 @@ 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, lod)
!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z)
){
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++){
for(int x = 0; x < 2 * lodMultiplitier; x++){
for(int y = 0; y < 2 * lodMultiplitier; 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);
posToCheck = new Vector3i(cell.getWorldPos()).add(lodMultiplitier,x,y);
} break;
case X_NEGATIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(0,x*highResSpacingFactor,y*highResSpacingFactor);
posToCheck = new Vector3i(cell.getWorldPos()).add(-1,x,y);
} break;
case Y_POSITIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,spacingFactor,y*highResSpacingFactor);
posToCheck = new Vector3i(cell.getWorldPos()).add(x,lodMultiplitier,y);
} break;
case Y_NEGATIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,0,y*highResSpacingFactor);
posToCheck = new Vector3i(cell.getWorldPos()).add(x,-1,y);
} break;
case Z_POSITIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,y*highResSpacingFactor,spacingFactor);
posToCheck = new Vector3i(cell.getWorldPos()).add(x,y,lodMultiplitier);
} break;
case Z_NEGATIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,y*highResSpacingFactor,0);
posToCheck = new Vector3i(cell.getWorldPos()).add(x,y,-1);
} break;
}
if(
@ -788,7 +594,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, highResLod)
!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z)
){
return false;
}
@ -879,14 +685,6 @@ 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,11 +87,6 @@ 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;
/**
@ -121,7 +116,7 @@ public class DrawCell {
boolean success = this.fillInData(lod);
Globals.profiler.endCpuSample();
if(!success){
this.setFailedGenerationAttempts(this.getFailedGenerationAttempts() + 1);
this.setHasRequested(false);
return;
}
if(modelEntity != null){
@ -134,7 +129,7 @@ public class DrawCell {
success = this.fillInFaceData(chunkData,face,lod);
Globals.profiler.endCpuSample();
if(!success){
this.setFailedGenerationAttempts(this.getFailedGenerationAttempts() + 1);
this.setHasRequested(false);
return;
}
}
@ -197,39 +192,23 @@ 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 / ChunkData.CHUNK_SIZE) * spacingFactor,
worldPos.y + (y / ChunkData.CHUNK_SIZE) * spacingFactor,
worldPos.z + (z / ChunkData.CHUNK_SIZE) * spacingFactor,
lod
worldPos.x + (x * spacingFactor) / ChunkData.CHUNK_SIZE,
worldPos.y + (y * spacingFactor) / ChunkData.CHUNK_SIZE,
worldPos.z + (z * spacingFactor) / ChunkData.CHUNK_SIZE
);
if(currentChunk == null){
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
);
return false;
}
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){
@ -254,30 +233,26 @@ public class DrawCell {
*/
private boolean fillInFaceData(TransvoxelChunkData chunkData, DrawCellFace higherLODFace, int lod){
int mainSpacing = (int)Math.pow(2,lod);
int higherLOD = lod - 1;
int higherResSpacing = (int)Math.pow(2,higherLOD);
int higherResSpacing = (int)Math.pow(2,lod - 1);
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 / ChunkData.CHUNK_SIZE * higherResSpacing;
int worldCoordOffset2 = y / ChunkData.CHUNK_SIZE * higherResSpacing;
int worldCoordOffset1 = (x * higherResSpacing) / ChunkData.CHUNK_SIZE;
int worldCoordOffset2 = (y * higherResSpacing) / ChunkData.CHUNK_SIZE;
//solve coordinates relative to the face
int localCoord1 = x % ChunkData.CHUNK_SIZE;
int localCoord2 = y % ChunkData.CHUNK_SIZE;
int localCoord1 = (x * higherResSpacing) % ChunkData.CHUNK_SIZE;
int localCoord2 = (y * higherResSpacing) % 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 + mainSpacing,
worldPos.y + worldCoordOffset1,
worldPos.z + worldCoordOffset2
),
higherLOD
);
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
worldPos.x + (17 * mainSpacing) / ChunkData.CHUNK_SIZE,
worldPos.y + worldCoordOffset1,
worldPos.z + worldCoordOffset2
));
if(currentChunk == null){
return false;
}
@ -293,14 +268,11 @@ public class DrawCell {
);
} break;
case X_NEGATIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
new Vector3i(
worldPos.x,
worldPos.y + worldCoordOffset1,
worldPos.z + worldCoordOffset2
),
higherLOD
);
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
worldPos.x,
worldPos.y + worldCoordOffset1,
worldPos.z + worldCoordOffset2
));
if(currentChunk == null){
return false;
}
@ -316,14 +288,11 @@ public class DrawCell {
);
} break;
case Y_POSITIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y + mainSpacing,
worldPos.z + worldCoordOffset2
),
higherLOD
);
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y + (17 * mainSpacing) / ChunkData.CHUNK_SIZE,
worldPos.z + worldCoordOffset2
));
if(currentChunk == null){
return false;
}
@ -339,14 +308,11 @@ public class DrawCell {
);
} break;
case Y_NEGATIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y,
worldPos.z + worldCoordOffset2
),
higherLOD
);
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y,
worldPos.z + worldCoordOffset2
));
if(currentChunk == null){
return false;
}
@ -362,14 +328,11 @@ public class DrawCell {
);
} break;
case Z_POSITIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y + worldCoordOffset2,
worldPos.z + mainSpacing
),
higherLOD
);
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y + worldCoordOffset2,
worldPos.z + (17 * mainSpacing) / ChunkData.CHUNK_SIZE
));
if(currentChunk == null){
return false;
}
@ -385,14 +348,11 @@ public class DrawCell {
);
} break;
case Z_NEGATIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y + worldCoordOffset2,
worldPos.z
),
higherLOD
);
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y + worldCoordOffset2,
worldPos.z
));
if(currentChunk == null){
return false;
}
@ -483,9 +443,6 @@ public class DrawCell {
*/
public void setHasRequested(boolean hasRequested) {
this.hasRequested = hasRequested;
if(!this.hasRequested){
this.failedGenerationAttempts = 0;
}
}
/**
@ -512,22 +469,6 @@ 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, ChunkData.NO_STRIDE)
!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z)
){
if(!requested.contains(requestKey)){
//client should request chunk data from server for each chunk necessary to create the model
@ -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,ChunkData.NO_STRIDE);
return Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldX,worldY,worldZ);
}
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,ChunkData.NO_STRIDE);
return Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldX,worldY,worldZ);
}

View File

@ -6,9 +6,7 @@ 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;
@ -27,7 +25,6 @@ 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
@ -41,16 +38,6 @@ 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;
@ -71,11 +58,6 @@ 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
@ -116,7 +98,7 @@ public class ClientTerrainManager {
}
}
}
ChunkData data = new ChunkData(message.getworldX(), message.getworldY(), message.getworldZ(), ChunkData.NO_STRIDE);
ChunkData data = new ChunkData(message.getworldX(), message.getworldY(), message.getworldZ());
data.setVoxelType(values);
data.setVoxelWeight(weights);
terrainCache.addChunkDataToCache(
@ -124,37 +106,6 @@ 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;
@ -163,15 +114,6 @@ 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();
}
@ -198,21 +140,19 @@ 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, int stride){
return terrainCache.containsChunkDataAtWorldPoint(worldX, worldY, worldZ, stride);
public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
return terrainCache.containsChunkDataAtWorldPoint(worldX, worldY, worldZ);
}
/**
* 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, int stride){
return this.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, stride);
public boolean containsChunkDataAtWorldPoint(Vector3i worldPos){
return terrainCache.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z);
}
/**
@ -220,24 +160,31 @@ 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 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(
public void requestChunk(int worldX, int worldY, int worldZ){
if(!this.terrainCache.hasRequested(worldX, worldY, worldZ)){
Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestChunkDataMessage(
worldX,
worldY,
worldZ,
stride
worldZ
));
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)
);
}
/**
@ -245,21 +192,19 @@ 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, int stride){
return terrainCache.getSubChunkDataAtPoint(worldX, worldY, worldZ, stride);
public ChunkData getChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
return terrainCache.getSubChunkDataAtPoint(worldX, worldY, worldZ);
}
/**
* 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, int stride){
return this.getChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, stride);
public ChunkData getChunkDataAtWorldPoint(Vector3i worldPos){
return terrainCache.getSubChunkDataAtPoint(worldPos.x, worldPos.y, worldPos.z);
}
@ -313,15 +258,11 @@ public class ClientTerrainManager {
}
/**
* 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
* Gets the number of chunks that have been requested
* @return The number of chunks
*/
private Long getRequestKey(int worldX, int worldY, int worldZ, int stride){
return (long)HashUtils.cantorHash(worldY, worldZ, worldZ);
public int getRequestedCellCount(){
return this.terrainCache.getRequestedCellCount();
}
}

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.NO_STRIDE)){
ChunkData chunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(chunkSpacePos, ChunkData.NO_STRIDE);
if(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(chunkSpacePos)){
ChunkData chunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(chunkSpacePos);
voxelId = chunkData.getType(voxelSpacePos);
} else {
return INVALID_POSITION;

View File

@ -4,7 +4,6 @@ import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import org.joml.Matrix4d;
import org.joml.Matrix4f;
import org.joml.Vector3f;
@ -258,7 +257,7 @@ public class Globals {
//matrices for drawing models
public static Matrix4f viewMatrix = new Matrix4f();
public static Matrix4d projectionMatrix;
public static Matrix4f 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(600000.0f);
EntityUtils.getScale(skybox).mul(200000.0f);
Globals.assetManager.queueOverrideMeshShader("Models/environment/skyboxSphere.fbx", "Sphere", "Shaders/entities/skysphere/skysphere.vs", "Shaders/entities/skysphere/skysphere.fs");
//cloud ring pseudo skybox
@ -297,14 +297,10 @@ public class ClientLoading {
Globals.clientSimulation.setLoadingTerrain(true);
//wait for all the terrain data to arrive
int i = 0;
while(
blockForInit &&
!Globals.clientDrawCellManager.isInitialized() &&
Globals.threadManager.shouldKeepRunning()
){
while(blockForInit && Globals.clientDrawCellManager.updatedLastFrame() && Globals.threadManager.shouldKeepRunning()){
i++;
if(i % DRAW_CELL_UPDATE_RATE == 0){
WindowUtils.updateLoadingWindow("WAITING ON SERVER TO SEND TERRAIN (" + Globals.clientTerrainManager.getAllChunks().size() + ")");
WindowUtils.updateLoadingWindow("WAITING ON SERVER TO SEND TERRAIN (" + Globals.clientTerrainManager.getAllChunks().size() + "/" + Globals.clientTerrainManager.getRequestedCellCount() + ")");
}
try {
TimeUnit.MILLISECONDS.sleep(10);

View File

@ -20,11 +20,6 @@ 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
@ -32,16 +27,14 @@ 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, int lod){
public TerrainChunkData(List<Float> vertices, List<Float> normals, List<Integer> faceElements, List<Float> uvs, List<Float> textureSamplers, List<Float> textureRatioVectors){
this.vertices = vertices;
this.normals = normals;
this.faceElements = faceElements;
this.uvs = uvs;
this.textureSamplers = textureSamplers;
this.textureRatioVectors = textureRatioVectors;
this.lod = lod;
}
/**
@ -92,12 +85,4 @@ public class TerrainChunkData {
return textureRatioVectors;
}
/**
* Gets the LOD of the model
* @return The LOD
*/
public int getLOD(){
return lod;
}
}

View File

@ -130,17 +130,6 @@ 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,10 +47,6 @@ 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
@ -83,8 +79,8 @@ public class TerrainProtocol implements ClientProtocolTemplate<TerrainMessage> {
}
//
//update the terrain cache
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(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z)){
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z);
if(data != null){
data.updatePosition(
message.getvoxelX(),
@ -98,8 +94,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.NO_STRIDE)){
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z, ChunkData.NO_STRIDE);
if(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z)){
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z);
if(data != null){
Globals.clientDrawCellManager.markUpdateable(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z);
}

View File

@ -7,7 +7,6 @@ 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;
@ -33,12 +32,6 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
);
return null;
}
case REQUESTREDUCEDCHUNKDATA: {
sendWorldSubChunkAsyncStrided(connectionHandler,
message.getworldX(), message.getworldY(), message.getworldZ(), message.getchunkResolution()
);
return null;
}
default: {
} break;
}
@ -192,65 +185,7 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
};
//request chunk
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);
realm.getServerWorldData().getServerTerrainManager().getChunkAsync(worldX, worldY, worldZ, onLoad);
Globals.profiler.endCpuSample();
}

View File

@ -1,7 +1,6 @@
package electrosphere.renderer;
import org.joml.FrustumIntersection;
import org.joml.Matrix4d;
import org.joml.Matrix4f;
import electrosphere.renderer.actor.instance.InstanceData;
@ -167,7 +166,7 @@ public class RenderPipelineState {
* @param projectionMatrix the projection matrix
* @param viewMatrix the view matrix
*/
public void updateFrustumIntersection(Matrix4d projectionMatrix, Matrix4f viewMatrix){
public void updateFrustumIntersection(Matrix4f 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 Matrix4d();
Globals.projectionMatrix = new Matrix4f();
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, 0);
TerrainChunkData rVal = new TerrainChunkData(vertsFlat, normalsFlat, elementsFlat, UVs, textureSamplers, textureRatioData);
return rVal;
}
@ -892,15 +892,14 @@ public class TerrainChunkModelGeneration {
//bounding sphere logic
int distance = ServerTerrainChunk.CHUNK_DIMENSION / 2 * (int)Math.pow(2,data.getLOD());
mesh.updateBoundingSphere(
distance,
distance,
distance,
ServerTerrainChunk.CHUNK_DIMENSION,
ServerTerrainChunk.CHUNK_DIMENSION,
ServerTerrainChunk.CHUNK_DIMENSION,
(float)Math.sqrt(
distance * distance +
distance * distance +
distance * distance
ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION +
ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION +
ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION
));

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, true);
polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert, false);
//
//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, true);
polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert, false);
}
}
} 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, chunkData.levelOfDetail);
TerrainChunkData rVal = new TerrainChunkData(vertsFlat, normalsFlat, elementsFlat, UVs, textureSamplers, textureRatioData);
return rVal;
}

View File

@ -22,7 +22,7 @@ public class DefaultChunkGenerator implements ChunkGenerator {
}
@Override
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ, int stride) {
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ) {
//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, int stride) {
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ) {
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,5 +1,6 @@
package electrosphere.server.terrain.generation;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@ -23,7 +24,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
/**
* The size of the realm for testing generation
*/
public static final int GENERATOR_REALM_SIZE = 512;
public static final int GENERATOR_REALM_SIZE = 32;
/**
* The default biome index
@ -64,14 +65,36 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
}
@Override
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ, int stride) {
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ) {
Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator.generateChunk");
ServerTerrainChunk rVal = null;
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];
float[][][] weights;
int[][][] values;
try {
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 {
//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);
@ -82,22 +105,11 @@ 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++){
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)
);
heightfield[x][z] = heightmapGen.getHeight(this.terrainModel.getSeed(), this.serverWorldData.convertVoxelToRealSpace(x, worldX), this.serverWorldData.convertVoxelToRealSpace(z, worldZ));
}
}
@ -105,22 +117,15 @@ 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++){
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);
GeneratedVoxel voxel = this.getVoxel(worldX, worldY, worldZ, x, y, z, heightfield, 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;
@ -139,7 +144,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 surfaceHeight The height of the surface at x,z
* @param heightfield The precomputed heightfield
* @param terrainModel The terrain model
* @param surfaceBiome The surface biome of the chunk
* @return The value of the chunk
@ -147,7 +152,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
private GeneratedVoxel getVoxel(
int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ,
float surfaceHeight,
float[][] heightfield,
TerrainModel terrainModel,
BiomeData surfaceBiome
){
@ -158,15 +163,16 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag());
}
double realX = this.serverWorldData.convertVoxelToRealSpace(chunkX,worldX);
double realX = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldX);
double realY = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldY);
double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkZ,worldZ);
double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkY,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,
@ -175,7 +181,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,
@ -184,7 +190,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,10 +13,9 @@ 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, int stride);
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ);
/**
* Sets the terrain model for the generation algorithm

View File

@ -4,7 +4,6 @@ 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;
@ -52,11 +51,6 @@ 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
@ -71,7 +65,6 @@ 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(
@ -79,7 +72,6 @@ public class ChunkGenerationThread implements Runnable {
ServerChunkCache chunkCache,
ChunkGenerator chunkGenerator,
int worldX, int worldY, int worldZ,
int stride,
Consumer<ServerTerrainChunk> onLoad
){
this.chunkDiskMap = chunkDiskMap;
@ -88,7 +80,6 @@ public class ChunkGenerationThread implements Runnable {
this.worldX = worldX;
this.worldY = worldY;
this.worldZ = worldZ;
this.stride = stride;
this.onLoad = onLoad;
}
@ -96,41 +87,37 @@ public class ChunkGenerationThread implements Runnable {
public void run() {
ServerTerrainChunk chunk = null;
int i = 0;
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);
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);
}
}
//generate if it does not exist
if(chunk == null){
try {
TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
} catch (InterruptedException e) {
e.printStackTrace();
}
chunk = chunkGenerator.generateChunk(worldX, worldY, worldZ);
}
if(chunk != null){
chunkCache.add(worldX, worldY, worldZ, chunk);
}
i++;
}
if(i >= MAX_TIME_TO_WAIT){
throw new Error("Failed to resolve chunk!");
if(chunk == null){
try {
TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.onLoad.accept(chunk);
} catch (Error e){
LoggerInterface.loggerEngine.ERROR(e);
i++;
}
if(i >= MAX_TIME_TO_WAIT){
throw new Error("Failed to resolve chunk!");
}
this.onLoad.accept(chunk);
}
}

View File

@ -6,11 +6,8 @@ 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
*/
@ -19,7 +16,7 @@ public class ServerChunkCache {
/**
* Number of chunks to cache
*/
static final int CACHE_SIZE = 2500;
static final int CACHE_SIZE = 5000;
/**
* The size of the cache
@ -27,39 +24,19 @@ public class ServerChunkCache {
int cacheSize = CACHE_SIZE;
/**
* The map of full res chunk key -> chunk data
* The cached data
*/
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>();
Map<String, ServerTerrainChunk> chunkCache = new HashMap<String,ServerTerrainChunk>();
/**
* Tracks how recently a chunk has been queries for (used for evicting old chunks from cache)
*/
List<Long> queryRecencyQueue = new LinkedList<Long>();
List<String> queryRecencyQueue = new LinkedList<String>();
/**
* Tracks what chunks are already queued to be asynchronously loaded. Used so we don't have two threads generating/fetching the same chunk
*/
Map<Long, Boolean> queuedChunkMap = new HashMap<Long,Boolean>();
Map<String, Boolean> queuedChunkMap = new HashMap<String,Boolean>();
/**
* The lock for thread safety
@ -72,7 +49,7 @@ public class ServerChunkCache {
*/
public Collection<ServerTerrainChunk> getContents(){
lock.acquireUninterruptibly();
Collection<ServerTerrainChunk> rVal = Collections.unmodifiableCollection(cacheMapFullRes.values());
Collection<ServerTerrainChunk> rVal = Collections.unmodifiableCollection(chunkCache.values());
lock.release();
return rVal;
}
@ -82,8 +59,7 @@ public class ServerChunkCache {
*/
public void clear(){
lock.acquireUninterruptibly();
cacheMapFullRes.clear();
cacheMapHalfRes.clear();
chunkCache.clear();
lock.release();
}
@ -92,17 +68,15 @@ 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, int stride){
public ServerTerrainChunk get(int worldX, int worldY, int worldZ){
ServerTerrainChunk rVal = null;
Long key = this.getKey(worldX, worldY, worldZ);
String key = this.getKey(worldX, worldY, worldZ);
lock.acquireUninterruptibly();
queryRecencyQueue.remove(key);
queryRecencyQueue.add(0, key);
Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
rVal = cache.get(key);
rVal = this.chunkCache.get(key);
lock.release();
return rVal;
}
@ -112,15 +86,13 @@ 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, int stride, ServerTerrainChunk chunk){
Long key = this.getKey(worldX, worldY, worldZ);
public void add(int worldX, int worldY, int worldZ, ServerTerrainChunk chunk){
String key = this.getKey(worldX, worldY, worldZ);
lock.acquireUninterruptibly();
queryRecencyQueue.add(0, key);
Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
cache.put(key, chunk);
this.chunkCache.put(key, chunk);
lock.release();
}
@ -129,14 +101,12 @@ 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, int stride){
Long key = this.getKey(worldX,worldY,worldZ);
public boolean containsChunk(int worldX, int worldY, int worldZ){
String key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly();
Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
boolean rVal = cache.containsKey(key);
boolean rVal = this.chunkCache.containsKey(key);
lock.release();
return rVal;
}
@ -148,8 +118,8 @@ public class ServerChunkCache {
* @param worldZ The z component
* @return The key
*/
public long getKey(int worldX, int worldY, int worldZ){
return HashUtils.cantorHash(worldX, worldY, worldZ);
public String getKey(int worldX, int worldY, int worldZ){
return worldX + "_" + worldY + "_" + worldZ;
}
/**
@ -160,7 +130,7 @@ public class ServerChunkCache {
* @return true if the chunk is already queued, false otherwise
*/
public boolean chunkIsQueued(int worldX, int worldY, int worldZ){
Long key = this.getKey(worldX,worldY,worldZ);
String key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly();
boolean rVal = this.queuedChunkMap.containsKey(key);
lock.release();
@ -174,7 +144,7 @@ public class ServerChunkCache {
* @param worldZ The world z position of the chunk
*/
public void queueChunk(int worldX, int worldY, int worldZ){
Long key = this.getKey(worldX,worldY,worldZ);
String key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly();
this.queuedChunkMap.put(key,true);
lock.release();
@ -185,41 +155,12 @@ 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, int stride){
Long key = this.getKey(worldX,worldY,worldZ);
public void unqueueChunk(int worldX, int worldY, int worldZ){
String 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,6 +1,5 @@
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;
@ -232,8 +231,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,ChunkData.NO_STRIDE)){
returnedChunk = chunkCache.get(worldX,worldY,worldZ, ChunkData.NO_STRIDE);
if(chunkCache.containsChunk(worldX,worldY,worldZ)){
returnedChunk = chunkCache.get(worldX,worldY,worldZ);
} else {
//pull from disk if it exists
if(chunkDiskMap != null){
@ -243,9 +242,9 @@ public class ServerTerrainManager {
}
//generate if it does not exist
if(returnedChunk == null){
returnedChunk = chunkGenerator.generateChunk(worldX, worldY, worldZ, ChunkData.NO_STRIDE);
returnedChunk = chunkGenerator.generateChunk(worldX, worldY, worldZ);
}
this.chunkCache.add(worldX, worldY, worldZ, ChunkData.NO_STRIDE, returnedChunk);
this.chunkCache.add(worldX, worldY, worldZ, returnedChunk);
}
Globals.profiler.endCpuSample();
return returnedChunk;
@ -256,12 +255,11 @@ 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, int stride, Consumer<ServerTerrainChunk> onLoad){
public void getChunkAsync(int worldX, int worldY, int worldZ, Consumer<ServerTerrainChunk> onLoad){
Globals.profiler.beginCpuSample("ServerTerrainManager.getChunkAsync");
this.chunkExecutorService.submit(new ChunkGenerationThread(chunkDiskMap, chunkCache, chunkGenerator, worldX, worldY, worldZ, stride, onLoad));
this.chunkExecutorService.submit(new ChunkGenerationThread(chunkDiskMap, chunkCache, chunkGenerator, worldX, worldY, worldZ, onLoad));
Globals.profiler.endCpuSample();
}
@ -289,8 +287,8 @@ public class ServerTerrainManager {
if(model != null){
model.addModification(modification);
}
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);
if(chunkCache.containsChunk(worldPos.x,worldPos.y,worldPos.z)){
ServerTerrainChunk chunk = chunkCache.get(worldPos.x,worldPos.y,worldPos.z);
chunk.addModification(modification);
}
}

View File

@ -127,13 +127,11 @@ public class TerrainModel {
TerrainModel rVal = new TerrainModel();
rVal.discreteArrayDimension = TestGenerationChunkGenerator.GENERATOR_REALM_SIZE;
rVal.dynamicInterpolationRatio = 1;
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;
}
}
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;
return rVal;
}