Merge pull request 'voxelImprovements' (#5) from voxelImprovements into master
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
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:
commit
afabf5595e
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
));
|
||||
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user