Terrain optimizations
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit

This commit is contained in:
austin 2024-11-11 10:23:49 -05:00
parent 4d6db78059
commit 1b5d6c1795
23 changed files with 847 additions and 228 deletions

View File

@ -1,3 +1,3 @@
#maven.buildNumber.plugin properties file
#Fri Nov 08 17:55:08 EST 2024
buildNumber=377
#Sun Nov 10 17:05:45 EST 2024
buildNumber=378

View File

@ -977,6 +977,17 @@ Memory debugging work + update memory flags in launch file
(11/10/2024)
Attempts at optimizing ClientDrawCellManager
Server-driven homogenous tracking to accelerate client draw cell manager
Face data fill bounds check optimization
Data fill skipping optimization
Homogenous node skipping optimization
Remove evaluation by distance
Squared distance optimization
Distance calculation caching optimization
Two layer destruction optimization
Non-reallocating list iteration for children in draw cell manager optimization
Split leaf/nonleaf tracks for node evaluation optimization
# TODO

View File

@ -90,6 +90,11 @@
"type" : "BYTE_ARRAY"
},
{
"name" : "homogenousValue",
"type" : "FIXED_INT"
},
{
"name" : "chunkResolution",
"type" : "FIXED_INT"
@ -209,6 +214,7 @@
"worldY",
"worldZ",
"chunkResolution",
"homogenousValue",
"chunkData"
]
},

View File

@ -13,7 +13,6 @@ import electrosphere.collision.PhysicsEntityUtils;
import electrosphere.engine.Globals;
import electrosphere.entity.EntityUtils;
import electrosphere.logger.LoggerInterface;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
import electrosphere.util.ds.octree.WorldOctTree;
import electrosphere.util.ds.octree.WorldOctTree.FloatingChunkTreeNode;
import electrosphere.util.math.GeomUtils;
@ -36,27 +35,27 @@ public class ClientDrawCellManager {
/**
* The distance to draw at full resolution
*/
public static final double FULL_RES_DIST = 8 * ServerTerrainChunk.CHUNK_DIMENSION;
public static final double FULL_RES_DIST = 8 * 8;
/**
* The distance for half resolution
*/
public static final double HALF_RES_DIST = 16 * ServerTerrainChunk.CHUNK_DIMENSION;
public static final double HALF_RES_DIST = 16 * 16;
/**
* The distance for quarter resolution
*/
public static final double QUARTER_RES_DIST = 20 * ServerTerrainChunk.CHUNK_DIMENSION;
public static final double QUARTER_RES_DIST = 20 * 20;
/**
* The distance for eighth resolution
*/
public static final double EIGHTH_RES_DIST = 32 * ServerTerrainChunk.CHUNK_DIMENSION;
public static final double EIGHTH_RES_DIST = 32 * 32;
/**
* The distance for sixteenth resolution
*/
public static final double SIXTEENTH_RES_DIST = 128 * ServerTerrainChunk.CHUNK_DIMENSION;
public static final double SIXTEENTH_RES_DIST = 128 * 128;
/**
* Lod value for a full res chunk
@ -99,9 +98,9 @@ public class ClientDrawCellManager {
Map<FloatingChunkTreeNode<DrawCell>,Boolean> evaluationMap = new HashMap<FloatingChunkTreeNode<DrawCell>,Boolean>();
/**
* All draw cells currently tracked
* The last recorded player world position
*/
List<DrawCell> activeCells = new LinkedList<DrawCell>();
Vector3i lastPlayerPos = new Vector3i();
/**
* Tracks whether the cell manager updated last frame or not
@ -166,6 +165,9 @@ public class ClientDrawCellManager {
Globals.profiler.beginCpuSample("ClientDrawCellManager.update");
if(shouldUpdate && Globals.playerEntity != null){
Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
Vector3i playerWorldPos = Globals.clientWorldData.convertRealToWorldSpace(playerPos);
int distCache = this.getDistCache(this.lastPlayerPos, playerWorldPos);
this.lastPlayerPos.set(playerWorldPos);
//the sets to iterate through
updatedLastFrame = true;
validCellCount = 0;
@ -173,31 +175,31 @@ public class ClientDrawCellManager {
//update all full res cells
FloatingChunkTreeNode<DrawCell> rootNode = this.chunkTree.getRoot();
Globals.profiler.beginCpuSample("ClientDrawCellManager.update - full res cells");
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, evaluationMap, FULL_RES_LOD);
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerWorldPos, evaluationMap, SIXTEENTH_RES_LOD, distCache);
Globals.profiler.endCpuSample();
if(!updatedLastFrame && !this.initialized){
this.initialized = true;
}
if(!updatedLastFrame){
Globals.profiler.beginCpuSample("ClientDrawCellManager.update - half res cells");
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, evaluationMap, HALF_RES_LOD);
Globals.profiler.endCpuSample();
}
if(!updatedLastFrame){
Globals.profiler.beginCpuSample("ClientDrawCellManager.update - half res cells");
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, evaluationMap, QUARTER_RES_LOD);
Globals.profiler.endCpuSample();
}
if(!updatedLastFrame){
Globals.profiler.beginCpuSample("ClientDrawCellManager.update - quarter res cells");
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, evaluationMap, EIGHTH_RES_LOD);
Globals.profiler.endCpuSample();
}
if(!updatedLastFrame){
Globals.profiler.beginCpuSample("ClientDrawCellManager.update - eighth res cells");
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, evaluationMap, SIXTEENTH_RES_LOD);
Globals.profiler.endCpuSample();
}
// if(!updatedLastFrame){
// Globals.profiler.beginCpuSample("ClientDrawCellManager.update - half res cells");
// updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerWorldPos, evaluationMap, HALF_RES_LOD, distCache);
// Globals.profiler.endCpuSample();
// }
// if(!updatedLastFrame){
// Globals.profiler.beginCpuSample("ClientDrawCellManager.update - half res cells");
// updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerWorldPos, evaluationMap, QUARTER_RES_LOD, distCache);
// Globals.profiler.endCpuSample();
// }
// if(!updatedLastFrame){
// Globals.profiler.beginCpuSample("ClientDrawCellManager.update - quarter res cells");
// updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerWorldPos, evaluationMap, EIGHTH_RES_LOD, distCache);
// Globals.profiler.endCpuSample();
// }
// if(!updatedLastFrame){
// Globals.profiler.beginCpuSample("ClientDrawCellManager.update - eighth res cells");
// updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerWorldPos, evaluationMap, SIXTEENTH_RES_LOD, distCache);
// Globals.profiler.endCpuSample();
// }
// if(!updatedLastFrame){
// Globals.profiler.beginCpuSample("ClientDrawCellManager.update - all res cells");
// updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, evaluationMap, ALL_RES_LOD);
@ -215,102 +217,117 @@ public class ClientDrawCellManager {
* @param evaluationMap Map of leaf nodes that have been evaluated this frame
* @return true if there is work remaining to be done, false otherwise
*/
private boolean recursivelyUpdateCells(FloatingChunkTreeNode<DrawCell> node, Vector3d playerPos, Map<FloatingChunkTreeNode<DrawCell>,Boolean> evaluationMap, int minLeafLod){
Vector3d playerRealPos = EntityUtils.getPosition(Globals.playerEntity);
private boolean recursivelyUpdateCells(FloatingChunkTreeNode<DrawCell> node, Vector3i playerPos, Map<FloatingChunkTreeNode<DrawCell>,Boolean> evaluationMap, int minLeafLod, int distCache){
boolean updated = false;
if(evaluationMap.containsKey(node)){
return false;
}
if(this.shouldSplit(playerPos, node)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.split");
//perform op
FloatingChunkTreeNode<DrawCell> container = chunkTree.split(node);
//do deletions
this.recursivelyDestroy(node);
//do creations
container.getChildren().forEach(child -> {
Vector3i cellWorldPos = new Vector3i(
child.getMinBound().x,
child.getMinBound().y,
child.getMinBound().z
);
DrawCell drawCell = DrawCell.generateTerrainCell(cellWorldPos);
activeCells.add(drawCell);
child.convertToLeaf(drawCell);
evaluationMap.put(child,true);
});
//update neighbors
this.conditionalUpdateAdjacentNodes(container, container.getChildren().get(0).getLevel());
Globals.profiler.endCpuSample();
updated = true;
} else if(this.shouldJoin(playerPos, node)) {
Globals.profiler.beginCpuSample("ClientDrawCellManager.join");
//perform op
FloatingChunkTreeNode<DrawCell> newLeaf = chunkTree.join(node);
//do deletions
this.recursivelyDestroy(node);
//do creations
DrawCell drawCell = DrawCell.generateTerrainCell(node.getMinBound());
activeCells.add(drawCell);
newLeaf.convertToLeaf(drawCell);
//update neighbors
this.conditionalUpdateAdjacentNodes(newLeaf, newLeaf.getLevel());
evaluationMap.put(newLeaf,true);
Globals.profiler.endCpuSample();
updated = true;
} else if(shouldRequest(playerPos, node, minLeafLod)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.request");
//calculate what to request
DrawCell cell = node.getData();
List<DrawCellFace> highResFaces = this.solveHighResFace(node);
//actually send requests
if(this.requestChunks(node, highResFaces)){
cell.setHasRequested(true);
}
evaluationMap.put(node,true);
Globals.profiler.endCpuSample();
updated = true;
} 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);
if(node.getData() != null && node.getData().hasGenerated() && node.getData().isHomogenous()){
return false;
}
if(node.isLeaf()){
if(this.shouldSplit(playerPos, node, distCache)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.split");
//perform op
FloatingChunkTreeNode<DrawCell> container = chunkTree.split(node);
//do deletions
this.twoLayerDestroy(node);
node.convertToLeaf(null);
//do creations
container.getChildren().forEach(child -> {
Vector3i cellWorldPos = new Vector3i(
child.getMinBound().x,
child.getMinBound().y,
child.getMinBound().z
);
DrawCell drawCell = DrawCell.generateTerrainCell(cellWorldPos,this.chunkTree.getMaxLevel() - child.getLevel());
child.convertToLeaf(drawCell);
evaluationMap.put(child,true);
});
//update neighbors
this.conditionalUpdateAdjacentNodes(container, container.getChildren().get(0).getLevel());
Globals.profiler.endCpuSample();
updated = true;
} else if(shouldRequest(playerPos, node, minLeafLod, distCache)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.request");
//calculate what to request
DrawCell cell = node.getData();
List<DrawCellFace> highResFaces = null;
if(shouldSolveFaces(node,playerPos, distCache)){
highResFaces = this.solveHighResFace(node);
}
} else if(node.getData() != null){
node.getData().setFailedGenerationAttempts(node.getData().getFailedGenerationAttempts() + 1);
if(node.getData().getFailedGenerationAttempts() > FAILED_GENERATION_ATTEMPT_THRESHOLD){
node.getData().setHasRequested(false);
//actually send requests
if(this.requestChunks(node, highResFaces)){
cell.setHasRequested(true);
}
}
evaluationMap.put(node,true);
Globals.profiler.endCpuSample();
updated = true;
} else if(!node.isLeaf()){
this.validCellCount++;
List<FloatingChunkTreeNode<DrawCell>> children = node.getChildren();
for(int i = 0; i < 8; i++){
FloatingChunkTreeNode<DrawCell> child = children.get(i);
boolean childUpdate = recursivelyUpdateCells(child, playerPos, evaluationMap, minLeafLod);
if(childUpdate == true){
updated = true;
}
}
if((this.chunkTree.getMaxLevel() - node.getLevel()) < minLeafLod){
evaluationMap.put(node,true);
Globals.profiler.endCpuSample();
updated = true;
} else if(shouldGenerate(playerPos, node, minLeafLod, distCache)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.generate");
int lodLevel = this.getLODLevel(node);
//high res faces
List<DrawCellFace> highResFaces = null;
if(shouldSolveFaces(node,playerPos, distCache)){
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);
}
}
evaluationMap.put(node,true);
Globals.profiler.endCpuSample();
updated = true;
}
} else {
if(this.shouldJoin(playerPos, node, distCache)) {
Globals.profiler.beginCpuSample("ClientDrawCellManager.join");
//perform op
FloatingChunkTreeNode<DrawCell> newLeaf = chunkTree.join(node);
//do deletions
this.twoLayerDestroy(node);
//do creations
DrawCell drawCell = DrawCell.generateTerrainCell(node.getMinBound(),this.chunkTree.getMaxLevel() - newLeaf.getLevel());
newLeaf.convertToLeaf(drawCell);
//update neighbors
this.conditionalUpdateAdjacentNodes(newLeaf, newLeaf.getLevel());
evaluationMap.put(newLeaf,true);
Globals.profiler.endCpuSample();
updated = true;
} else {
this.validCellCount++;
List<FloatingChunkTreeNode<DrawCell>> children = node.getChildren();
for(int i = 0; i < 8; i++){
FloatingChunkTreeNode<DrawCell> child = children.get(i);
boolean childUpdate = recursivelyUpdateCells(child, playerPos, evaluationMap, minLeafLod, distCache);
if(childUpdate == true){
updated = true;
}
}
if((this.chunkTree.getMaxLevel() - node.getLevel()) < minLeafLod){
evaluationMap.put(node,true);
}
}
}
return updated;
@ -322,10 +339,47 @@ public class ClientDrawCellManager {
* @param node the node
* @return the distance
*/
public double getMinDistance(Vector3d pos, FloatingChunkTreeNode<DrawCell> node){
Vector3i min = node.getMinBound();
Vector3i max = node.getMaxBound();
return GeomUtils.getMinDistanceAABB(pos, Globals.clientWorldData.convertWorldToRealSpace(min), Globals.clientWorldData.convertWorldToRealSpace(max));
public double getMinDistance(Vector3i worldPos, FloatingChunkTreeNode<DrawCell> node, int distCache){
if(node.getData() == null){
return GeomUtils.getMinSquaredDistanceAABB(worldPos, node.getMinBound(), node.getMaxBound());
} else {
return node.getData().getMinDistance(worldPos, node, distCache);
}
}
/**
* Gets the distance cache value
* @param lastPlayerPos The last player world position
* @param currentPlayerPos The current player world position
* @return The distance cache value
*/
private int getDistCache(Vector3i lastPlayerPos, Vector3i currentPlayerPos){
if(
lastPlayerPos.x / 16 != currentPlayerPos.x / 16 || lastPlayerPos.z / 16 != currentPlayerPos.z / 16 || lastPlayerPos.z / 16 != currentPlayerPos.z / 16
){
return SIXTEENTH_RES_LOD;
}
if(
lastPlayerPos.x / 8 != currentPlayerPos.x / 8 || lastPlayerPos.z / 8 != currentPlayerPos.z / 8 || lastPlayerPos.z / 8 != currentPlayerPos.z / 8
){
return EIGHTH_RES_LOD;
}
if(
lastPlayerPos.x / 4 != currentPlayerPos.x / 4 || lastPlayerPos.z / 4 != currentPlayerPos.z / 4 || lastPlayerPos.z / 4 != currentPlayerPos.z / 4
){
return SIXTEENTH_RES_LOD;
}
if(
lastPlayerPos.x / 2 != currentPlayerPos.x / 2 || lastPlayerPos.z / 2 != currentPlayerPos.z / 2 || lastPlayerPos.z / 2 != currentPlayerPos.z / 2
){
return EIGHTH_RES_LOD;
}
if(
lastPlayerPos.x != currentPlayerPos.x || lastPlayerPos.z != currentPlayerPos.z || lastPlayerPos.z != currentPlayerPos.z
){
return QUARTER_RES_LOD;
}
return -1;
}
/**
@ -334,36 +388,38 @@ public class ClientDrawCellManager {
* @param node The node
* @return true if should split, false otherwise
*/
public boolean shouldSplit(Vector3d pos, FloatingChunkTreeNode<DrawCell> node){
public boolean shouldSplit(Vector3i pos, FloatingChunkTreeNode<DrawCell> node, int distCache){
//breaking out into dedicated function so can add case handling ie if we want
//to combine fullres nodes into larger nodes to conserve on draw calls
return
node.isLeaf() &&
node.canSplit() &&
(node.getLevel() != this.chunkTree.getMaxLevel()) &&
(node.getData() == null || !node.getData().hasGenerated() || !node.getData().isHomogenous()) &&
(node.getParent() != null || node == this.chunkTree.getRoot()) &&
(
(
node.getLevel() < this.chunkTree.getMaxLevel() - SIXTEENTH_RES_LOD &&
this.getMinDistance(pos, node) <= SIXTEENTH_RES_DIST
this.getMinDistance(pos, node, distCache) <= SIXTEENTH_RES_DIST
)
||
(
node.getLevel() < this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD &&
this.getMinDistance(pos, node) <= EIGHTH_RES_DIST
this.getMinDistance(pos, node, distCache) <= EIGHTH_RES_DIST
)
||
(
node.getLevel() < this.chunkTree.getMaxLevel() - QUARTER_RES_LOD &&
this.getMinDistance(pos, node) <= QUARTER_RES_DIST
this.getMinDistance(pos, node, distCache) <= QUARTER_RES_DIST
)
||
(
node.getLevel() < this.chunkTree.getMaxLevel() - HALF_RES_LOD &&
this.getMinDistance(pos, node) <= HALF_RES_DIST
this.getMinDistance(pos, node, distCache) <= HALF_RES_DIST
)
||
(
node.getLevel() < this.chunkTree.getMaxLevel() &&
this.getMinDistance(pos, node) <= FULL_RES_DIST
this.getMinDistance(pos, node, distCache) <= FULL_RES_DIST
)
)
;
@ -371,14 +427,46 @@ public class ClientDrawCellManager {
/**
* Gets the LOD level of the draw cell
* @param pos The position of the cell
* @param node The node to consider
* @return -1 if outside of render range, -1 if the node is not a valid draw cell leaf, otherwise returns the LOD level
*/
private int getLODLevel(Vector3d pos, FloatingChunkTreeNode<DrawCell> node){
private int getLODLevel(FloatingChunkTreeNode<DrawCell> node){
return this.chunkTree.getMaxLevel() - node.getLevel();
}
/**
* Tracks whether the high res faces should be solved for or not
* @param node The node
* @param playerWorldPos The player's world position
* @return true if should solve for high res faces, false otherwise
*/
private boolean shouldSolveFaces(FloatingChunkTreeNode<DrawCell> node, Vector3i playerWorldPos, int distCache){
if(node.getLevel() == this.chunkTree.getMaxLevel()){
return false;
}
int lod = this.chunkTree.getMaxLevel() - node.getLevel();
if(lod > SIXTEENTH_RES_LOD){
return false;
}
switch(lod){
case HALF_RES_LOD: {
return this.getMinDistance(playerWorldPos, node, distCache) > HALF_RES_DIST;
}
case QUARTER_RES_LOD: {
return this.getMinDistance(playerWorldPos, node, distCache) > QUARTER_RES_DIST;
}
case EIGHTH_RES_LOD: {
return this.getMinDistance(playerWorldPos, node, distCache) > EIGHTH_RES_DIST;
}
case SIXTEENTH_RES_LOD: {
return this.getMinDistance(playerWorldPos, node, distCache) > SIXTEENTH_RES_DIST;
}
default: {
throw new Error("Unsupported lod: " + lod);
}
}
}
/**
* Solves which face (if any) is the high res face for a LOD chunk
* @param node The node for the chunk
@ -389,6 +477,9 @@ public class ClientDrawCellManager {
if(node.getLevel() == this.chunkTree.getMaxLevel()){
return null;
}
if(this.chunkTree.getMaxLevel() - node.getLevel() > SIXTEENTH_RES_LOD){
return null;
}
int lodMultiplitier = this.chunkTree.getMaxLevel() - node.getLevel() + 1;
int spacing = (int)Math.pow(2,lodMultiplitier);
List<DrawCellFace> faces = new LinkedList<DrawCellFace>();
@ -488,35 +579,35 @@ public class ClientDrawCellManager {
* @param node The node
* @return true if should be joined, false otherwise
*/
public boolean shouldJoin(Vector3d pos, FloatingChunkTreeNode<DrawCell> node){
public boolean shouldJoin(Vector3i pos, FloatingChunkTreeNode<DrawCell> node, int distCache){
//breaking out into dedicated function so can add case handling ie if we want
//to combine fullres nodes into larger nodes to conserve on draw calls
return
node.getLevel() > 0 &&
!node.isLeaf() &&
(node.getLevel() != this.chunkTree.getMaxLevel()) &&
(
(
node.getLevel() == this.chunkTree.getMaxLevel() - HALF_RES_LOD &&
this.getMinDistance(pos, node) > FULL_RES_DIST
this.getMinDistance(pos, node, distCache) > FULL_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - QUARTER_RES_LOD &&
this.getMinDistance(pos, node) > HALF_RES_DIST
this.getMinDistance(pos, node, distCache) > HALF_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD &&
this.getMinDistance(pos, node) > QUARTER_RES_DIST
this.getMinDistance(pos, node, distCache) > QUARTER_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - SIXTEENTH_RES_LOD &&
this.getMinDistance(pos, node) > EIGHTH_RES_DIST
this.getMinDistance(pos, node, distCache) > EIGHTH_RES_DIST
)
||
(
this.getMinDistance(pos, node) > SIXTEENTH_RES_DIST
this.getMinDistance(pos, node, distCache) > SIXTEENTH_RES_DIST
)
)
;
@ -529,9 +620,8 @@ public class ClientDrawCellManager {
* @param minLeafLod The minimum LOD required to evaluate a leaf
* @return true if should request chunk data, false otherwise
*/
public boolean shouldRequest(Vector3d pos, FloatingChunkTreeNode<DrawCell> node, int minLeafLod){
public boolean shouldRequest(Vector3i pos, FloatingChunkTreeNode<DrawCell> node, int minLeafLod, int distCache){
return
node.isLeaf() &&
node.getData() != null &&
!node.getData().hasRequested() &&
(this.chunkTree.getMaxLevel() - node.getLevel()) <= minLeafLod &&
@ -545,25 +635,25 @@ public class ClientDrawCellManager {
(
node.getLevel() == this.chunkTree.getMaxLevel() - HALF_RES_LOD
&&
this.getMinDistance(pos, node) <= QUARTER_RES_DIST
this.getMinDistance(pos, node, distCache) <= QUARTER_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - QUARTER_RES_LOD
&&
this.getMinDistance(pos, node) <= EIGHTH_RES_DIST
this.getMinDistance(pos, node, distCache) <= EIGHTH_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD
&&
this.getMinDistance(pos, node) <= SIXTEENTH_RES_DIST
this.getMinDistance(pos, node, distCache) <= SIXTEENTH_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - SIXTEENTH_RES_LOD
&&
this.getMinDistance(pos, node) <= SIXTEENTH_RES_DIST
this.getMinDistance(pos, node, distCache) <= SIXTEENTH_RES_DIST
)
)
;
@ -576,9 +666,8 @@ public class ClientDrawCellManager {
* @param minLeafLod The minimum LOD required to evaluate a leaf
* @return true if should generate, false otherwise
*/
public boolean shouldGenerate(Vector3d pos, FloatingChunkTreeNode<DrawCell> node, int minLeafLod){
public boolean shouldGenerate(Vector3i pos, FloatingChunkTreeNode<DrawCell> node, int minLeafLod, int distCache){
return
node.isLeaf() &&
node.getData() != null &&
!node.getData().hasGenerated() &&
(this.chunkTree.getMaxLevel() - node.getLevel()) <= minLeafLod &&
@ -592,25 +681,25 @@ public class ClientDrawCellManager {
(
node.getLevel() == this.chunkTree.getMaxLevel() - HALF_RES_LOD
&&
this.getMinDistance(pos, node) <= QUARTER_RES_DIST
this.getMinDistance(pos, node, distCache) <= QUARTER_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - QUARTER_RES_LOD
&&
this.getMinDistance(pos, node) <= EIGHTH_RES_DIST
this.getMinDistance(pos, node, distCache) <= EIGHTH_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD
&&
this.getMinDistance(pos, node) <= SIXTEENTH_RES_DIST
this.getMinDistance(pos, node, distCache) <= SIXTEENTH_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - SIXTEENTH_RES_LOD
&&
this.getMinDistance(pos, node) <= SIXTEENTH_RES_DIST
this.getMinDistance(pos, node, distCache) <= SIXTEENTH_RES_DIST
)
)
;
@ -644,7 +733,22 @@ public class ClientDrawCellManager {
node.getChildren().forEach(child -> recursivelyDestroy(child));
}
if(node.getData() != null){
activeCells.remove(node.getData());
node.getData().destroy();
}
}
/**
* Destroys two layers of nodes
* @param node The top node
*/
private void twoLayerDestroy(FloatingChunkTreeNode<DrawCell> node){
if(node.getData() == null){
for(FloatingChunkTreeNode<DrawCell> child : node.getChildren()){
if(child.getData() != null){
child.getData().destroy();
}
}
} else {
node.getData().destroy();
}
}

View File

@ -14,6 +14,8 @@ import electrosphere.entity.Entity;
import electrosphere.entity.types.terrain.TerrainChunk;
import electrosphere.renderer.meshgen.TransvoxelModelGeneration;
import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData;
import electrosphere.util.ds.octree.WorldOctTree.FloatingChunkTreeNode;
import electrosphere.util.math.GeomUtils;
/**
* A single drawcell - contains an entity that has a physics mesh and potentially graphics
@ -34,6 +36,12 @@ public class DrawCell {
//the position of the draw cell in world coordinates
Vector3i worldPos;
/**
* The LOD of the draw cell
*/
int lod;
//the main entity for the cell
Entity modelEntity;
@ -63,10 +71,30 @@ public class DrawCell {
*/
boolean multiVoxelType = false;
/**
* Tracks whether this draw cell is flagged as homogenous from the server or not
*/
boolean homogenous = true;
/**
* Number of failed generation attempts
*/
int failedGenerationAttempts = 0;
/**
* The number of valid fill lookups
*/
int validLookups = 0;
/**
* Labels an invalid distance cache
*/
static final int INVALID_DIST_CACHE = -1;
/**
* The cached minimum distance
*/
double cachedMinDistance = -1;
/**
@ -81,9 +109,11 @@ public class DrawCell {
* Constructs a drawcell object
*/
public static DrawCell generateTerrainCell(
Vector3i worldPos
Vector3i worldPos,
int lod
){
DrawCell rVal = new DrawCell();
rVal.lod = lod;
rVal.worldPos = worldPos;
return rVal;
}
@ -116,6 +146,8 @@ public class DrawCell {
}
modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(chunkData, lod, atlas, this.hasPolygons());
ClientEntityUtils.initiallyPositionEntity(modelEntity, getRealPos(), new Quaterniond());
// this.weights = null;
// this.types = null;
this.setHasGenerated(true);
}
@ -164,10 +196,26 @@ public class DrawCell {
* @return true if the data was successfully filled, false otherwise
*/
private boolean fillInData(int lod){
// if(lod == ClientDrawCellManager.FULL_RES_LOD){
ChunkData homogenousLookupChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
worldPos.x,
worldPos.y,
worldPos.z,
lod
);
if(homogenousLookupChunk != null && homogenousLookupChunk.getHomogenousValue() != ChunkData.NOT_HOMOGENOUS){
return true;
}
// }
int spacingFactor = (int)Math.pow(2,lod);
for(int x = 0; x < ChunkData.CHUNK_DATA_GENERATOR_SIZE; x++){
int i = 0;
for(int x = i / ChunkData.CHUNK_DATA_GENERATOR_SIZE / ChunkData.CHUNK_DATA_GENERATOR_SIZE; x < ChunkData.CHUNK_DATA_GENERATOR_SIZE; x++){
for(int y = 0; y < ChunkData.CHUNK_DATA_GENERATOR_SIZE; y++){
for(int z = 0; z < ChunkData.CHUNK_DATA_GENERATOR_SIZE; z++){
if(i < validLookups){
i++;
continue;
}
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
worldPos.x + (x / ChunkData.CHUNK_SIZE) * spacingFactor,
worldPos.y + (y / ChunkData.CHUNK_SIZE) * spacingFactor,
@ -201,8 +249,14 @@ public class DrawCell {
y % ChunkData.CHUNK_SIZE,
z % ChunkData.CHUNK_SIZE
);
if(currentChunk.getHomogenousValue() == ChunkData.NOT_HOMOGENOUS){
this.homogenous = false;
}
i++;
validLookups++;
}
//checks to see if there is only one type of voxel in this chunk
if(!this.multiVoxelType){
if(this.initialVoxelType == -1){
@ -253,6 +307,9 @@ public class DrawCell {
if(currentChunk == null){
return false;
}
if(currentChunk.getHomogenousValue() == ChunkData.NOT_HOMOGENOUS){
this.homogenous = false;
}
faceWeights[x][y] = currentChunk.getWeight(
0,
localCoord1,
@ -276,6 +333,9 @@ public class DrawCell {
if(currentChunk == null){
return false;
}
if(currentChunk.getHomogenousValue() == ChunkData.NOT_HOMOGENOUS){
this.homogenous = false;
}
faceWeights[x][y] = currentChunk.getWeight(
0,
localCoord1,
@ -299,6 +359,9 @@ public class DrawCell {
if(currentChunk == null){
return false;
}
if(currentChunk.getHomogenousValue() == ChunkData.NOT_HOMOGENOUS){
this.homogenous = false;
}
faceWeights[x][y] = currentChunk.getWeight(
localCoord1,
0,
@ -322,6 +385,9 @@ public class DrawCell {
if(currentChunk == null){
return false;
}
if(currentChunk.getHomogenousValue() == ChunkData.NOT_HOMOGENOUS){
this.homogenous = false;
}
faceWeights[x][y] = currentChunk.getWeight(
localCoord1,
0,
@ -345,6 +411,9 @@ public class DrawCell {
if(currentChunk == null){
return false;
}
if(currentChunk.getHomogenousValue() == ChunkData.NOT_HOMOGENOUS){
this.homogenous = false;
}
faceWeights[x][y] = currentChunk.getWeight(
localCoord1,
localCoord2,
@ -368,6 +437,9 @@ public class DrawCell {
if(currentChunk == null){
return false;
}
if(currentChunk.getHomogenousValue() == ChunkData.NOT_HOMOGENOUS){
this.homogenous = false;
}
faceWeights[x][y] = currentChunk.getWeight(
localCoord1,
localCoord2,
@ -483,6 +555,29 @@ public class DrawCell {
this.failedGenerationAttempts = this.failedGenerationAttempts + attempts;
}
/**
* Gets whether this draw cell is homogenous or not
* @return true if it is homogenous, false otherwise
*/
public boolean isHomogenous(){
return homogenous;
}
/**
* Gets the minimum distance from a node to a point
* @param pos the position to check against
* @param node the node
* @param distCache the lod value under which distance caches are invalidated
* @return the distance
*/
public double getMinDistance(Vector3i worldPos, FloatingChunkTreeNode<DrawCell> node, int distCache){
if(cachedMinDistance != INVALID_DIST_CACHE && distCache < lod){
return cachedMinDistance;
} else {
this.cachedMinDistance = GeomUtils.getMinSquaredDistanceAABB(worldPos, node.getMinBound(), node.getMaxBound());
return cachedMinDistance;
}
}
}

View File

@ -233,7 +233,8 @@ public class DrawCellManager {
//build the cell
DrawCell cell = DrawCell.generateTerrainCell(
worldPos
worldPos,
0
);
cells.add(cell);
keyCellMap.put(targetKey,cell);

View File

@ -55,7 +55,7 @@ public class ClientTerrainManager {
public static final int INTERPOLATION_RATIO = ServerTerrainManager.SERVER_TERRAIN_MANAGER_INTERPOLATION_RATIO;
//caches chunks from server
static final int CACHE_SIZE = 2500 + (int)(ClientDrawCellManager.FULL_RES_DIST) + (int)(ClientDrawCellManager.HALF_RES_DIST);
static final int CACHE_SIZE = 5000 + (int)(ClientDrawCellManager.FULL_RES_DIST * 10) + (int)(ClientDrawCellManager.HALF_RES_DIST * 10);
/**
* Size of the cache in bytes

View File

@ -56,7 +56,7 @@ public class ImGuiChunkMonitor {
ImGui.text("Chunk Monitor");
Globals.clientDrawCellManager.updateStatus();
// ImGui.text("Full res chunks: " + Globals.clientDrawCellManager.getFullResCacheSize());
ImGui.text("Full res chunks: " + Globals.clientDrawCellManager.getMaxResCount());
// ImGui.text("Garbage queue: " + Globals.clientDrawCellManager.getGarbageSize());

View File

@ -69,7 +69,7 @@ public class ClientEditorMovementTree implements BehaviorTree {
static final double STATE_DIFFERENCE_CREEP_MULTIPLIER = 0.001; //while the movement tree is idle, slowly creep the position of the entity towards the true server position by this amount
static final double STATE_DIFFERENCE_CREEP_CUTOFF = 0.01; //the cutoff for creep when we say it's "close enough"
public static final float EDITOR_MAX_VELOCITY = 1.0f;
public static final float EDITOR_MAX_VELOCITY = 0.5f;
public static final float EDITOR_ACCEL = 1.0f;
String animationStartUp = Animation.ANIMATION_MOVEMENT_STARTUP;

View File

@ -50,6 +50,7 @@ public class TerrainChunk {
TerrainChunkData data;
try {
data = TransvoxelModelGeneration.generateTerrainChunkData(chunkData);
data.constructBuffers();
if(Globals.clientScene.containsEntity(rVal)){
String modelPath = ClientTerrainManager.queueTerrainGridGeneration(data, atlas);
EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath);

View File

@ -1,7 +1,11 @@
package electrosphere.entity.types.terrain;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.List;
import org.lwjgl.BufferUtils;
/**
* The data required to generate a texture
*/
@ -20,6 +24,17 @@ public class TerrainChunkData {
//texture ratio vector
List<Float> textureRatioVectors; //HOW MUCH of each texture in the atlas to sample
/**
* The various buffers of data to send to the gpu
*/
FloatBuffer vertexArrayBufferData = null;
FloatBuffer normalArrayBufferData = null;
FloatBuffer textureArrayBufferData = null;
IntBuffer elementArrayBufferData = null;
FloatBuffer samplerBuffer = null;
FloatBuffer ratioBuffer = null;
/**
* The LOD of the model
*/
@ -44,6 +59,49 @@ public class TerrainChunkData {
this.lod = lod;
}
/**
* Allocates and fills the buffers to send to the gpu
*/
public void constructBuffers(){
int elementCount = this.getFaceElements().size();
vertexArrayBufferData = BufferUtils.createFloatBuffer(elementCount * 3);
normalArrayBufferData = BufferUtils.createFloatBuffer(elementCount * 3);
textureArrayBufferData = BufferUtils.createFloatBuffer(elementCount * 2);
elementArrayBufferData = BufferUtils.createIntBuffer(elementCount);
int incrementer = 0;
for(int element : this.getFaceElements()){
//for each element, need to push vert, normal, etc
//push current vertex
vertexArrayBufferData.put(this.getVertices().get(element*3+0));
vertexArrayBufferData.put(this.getVertices().get(element*3+1));
vertexArrayBufferData.put(this.getVertices().get(element*3+2));
//push current normals
normalArrayBufferData.put(this.getNormals().get(element*3+0));
normalArrayBufferData.put(this.getNormals().get(element*3+1));
normalArrayBufferData.put(this.getNormals().get(element*3+2));
//push current uvs
textureArrayBufferData.put(this.getUVs().get(element*2+0));
textureArrayBufferData.put(this.getUVs().get(element*2+1));
//push faces -- instead of pushing the element directly, instead use the incrementer because we want to draw a new vertex
//there should be no vertex-sharing between triangles. This keeps the meshes from blurring texture/lighting
elementArrayBufferData.put(incrementer);
incrementer++;
}
int vertexCount = this.getTextureSamplers().size() / 3;
samplerBuffer = BufferUtils.createFloatBuffer(vertexCount * 3);
for(float samplerVec : this.getTextureSamplers()){
samplerBuffer.put(samplerVec);
}
ratioBuffer = BufferUtils.createFloatBuffer(vertexCount * 3);
for(float ratioVec : this.getTextureRatioVectors()){
ratioBuffer.put(ratioVec);
}
}
/**
* Gets the vertex data
* @return the vertex data
@ -100,4 +158,29 @@ public class TerrainChunkData {
return lod;
}
public FloatBuffer getVertexArrayBufferData() {
return vertexArrayBufferData;
}
public FloatBuffer getNormalArrayBufferData() {
return normalArrayBufferData;
}
public FloatBuffer getTextureArrayBufferData() {
return textureArrayBufferData;
}
public IntBuffer getElementArrayBufferData() {
return elementArrayBufferData;
}
public FloatBuffer getSamplerBuffer() {
return samplerBuffer;
}
public FloatBuffer getRatioBuffer() {
return ratioBuffer;
}
}

View File

@ -48,6 +48,7 @@ public class TerrainMessage extends NetworkMessage {
double realLocationY;
double realLocationZ;
byte[] chunkData;
int homogenousValue;
int chunkResolution;
float terrainWeight;
int terrainValue;
@ -317,6 +318,20 @@ public class TerrainMessage extends NetworkMessage {
this.chunkData = chunkData;
}
/**
* Gets homogenousValue
*/
public int gethomogenousValue() {
return homogenousValue;
}
/**
* Sets homogenousValue
*/
public void sethomogenousValue(int homogenousValue) {
this.homogenousValue = homogenousValue;
}
/**
* Gets chunkResolution
*/
@ -738,17 +753,20 @@ public class TerrainMessage extends NetworkMessage {
if(currentStreamLength < 18){
return false;
}
int chunkDataSize = 0;
if(currentStreamLength < 22){
return false;
}
int chunkDataSize = 0;
if(currentStreamLength < 26){
return false;
} else {
temporaryByteQueue.add(byteBuffer.peek(18 + 0));
temporaryByteQueue.add(byteBuffer.peek(18 + 1));
temporaryByteQueue.add(byteBuffer.peek(18 + 2));
temporaryByteQueue.add(byteBuffer.peek(18 + 3));
temporaryByteQueue.add(byteBuffer.peek(22 + 0));
temporaryByteQueue.add(byteBuffer.peek(22 + 1));
temporaryByteQueue.add(byteBuffer.peek(22 + 2));
temporaryByteQueue.add(byteBuffer.peek(22 + 3));
chunkDataSize = ByteStreamUtils.popIntFromByteQueue(temporaryByteQueue);
}
if(currentStreamLength < 22 + chunkDataSize){
if(currentStreamLength < 26 + chunkDataSize){
return false;
}
return true;
@ -764,6 +782,7 @@ public class TerrainMessage extends NetworkMessage {
rVal.setworldY(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
rVal.setworldZ(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
rVal.setchunkResolution(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
rVal.sethomogenousValue(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
rVal.setchunkData(ByteStreamUtils.popByteArrayFromByteQueue(byteBuffer));
return rVal;
}
@ -771,12 +790,13 @@ public class TerrainMessage extends NetworkMessage {
/**
* Constructs a message of type SendReducedChunkData
*/
public static TerrainMessage constructSendReducedChunkDataMessage(int worldX,int worldY,int worldZ,int chunkResolution,byte[] chunkData){
public static TerrainMessage constructSendReducedChunkDataMessage(int worldX,int worldY,int worldZ,int chunkResolution,int homogenousValue,byte[] chunkData){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDREDUCEDCHUNKDATA);
rVal.setworldX(worldX);
rVal.setworldY(worldY);
rVal.setworldZ(worldZ);
rVal.setchunkResolution(chunkResolution);
rVal.sethomogenousValue(homogenousValue);
rVal.setchunkData(chunkData);
rVal.serialize();
return rVal;
@ -1158,7 +1178,7 @@ public class TerrainMessage extends NetworkMessage {
}
break;
case SENDREDUCEDCHUNKDATA:
rawBytes = new byte[2+4+4+4+4+4+chunkData.length];
rawBytes = new byte[2+4+4+4+4+4+4+chunkData.length];
//message header
rawBytes[0] = TypeBytes.MESSAGE_TYPE_TERRAIN;
//entity messaage header
@ -1179,12 +1199,16 @@ public class TerrainMessage extends NetworkMessage {
for(int i = 0; i < 4; i++){
rawBytes[14+i] = intValues[i];
}
intValues = ByteStreamUtils.serializeIntToBytes(chunkData.length);
intValues = ByteStreamUtils.serializeIntToBytes(homogenousValue);
for(int i = 0; i < 4; i++){
rawBytes[18+i] = intValues[i];
}
intValues = ByteStreamUtils.serializeIntToBytes(chunkData.length);
for(int i = 0; i < 4; i++){
rawBytes[22+i] = intValues[i];
}
for(int i = 0; i < chunkData.length; i++){
rawBytes[22+i] = chunkData[i];
rawBytes[26+i] = chunkData[i];
}
break;
case REQUESTFLUIDDATA:

View File

@ -246,7 +246,7 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
// 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()));
connectionHandler.addMessagetoOutgoingQueue(TerrainMessage.constructSendReducedChunkDataMessage(worldX, worldY, worldZ, stride, chunk.getHomogenousValue(), buffer.array()));
};
//request chunk

View File

@ -9,7 +9,6 @@ import java.util.Map;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
@ -785,7 +784,12 @@ public class TerrainChunkModelGeneration {
FloatBuffer vertexArrayBufferData = data.getVertexArrayBufferData();
FloatBuffer normalArrayBufferData = data.getNormalArrayBufferData();
FloatBuffer textureArrayBufferData = data.getTextureArrayBufferData();
IntBuffer elementArrayBufferData = data.getElementArrayBufferData();
FloatBuffer samplerBuffer = data.getSamplerBuffer();
FloatBuffer ratioBuffer = data.getRatioBuffer();
@ -795,33 +799,6 @@ public class TerrainChunkModelGeneration {
//
int elementCount = data.getFaceElements().size();
try {
FloatBuffer vertexArrayBufferData = BufferUtils.createFloatBuffer(elementCount * 3);
FloatBuffer normalArrayBufferData = BufferUtils.createFloatBuffer(elementCount * 3);
FloatBuffer textureArrayBufferData = BufferUtils.createFloatBuffer(elementCount * 2);
IntBuffer elementArrayBufferData = BufferUtils.createIntBuffer(elementCount);
int incrementer = 0;
for(int element : data.getFaceElements()){
//for each element, need to push vert, normal, etc
//push current vertex
vertexArrayBufferData.put(data.getVertices().get(element*3+0));
vertexArrayBufferData.put(data.getVertices().get(element*3+1));
vertexArrayBufferData.put(data.getVertices().get(element*3+2));
//push current normals
normalArrayBufferData.put(data.getNormals().get(element*3+0));
normalArrayBufferData.put(data.getNormals().get(element*3+1));
normalArrayBufferData.put(data.getNormals().get(element*3+2));
//push current uvs
textureArrayBufferData.put(data.getUVs().get(element*2+0));
textureArrayBufferData.put(data.getUVs().get(element*2+1));
//push faces -- instead of pushing the element directly, instead use the incrementer because we want to draw a new vertex
//there should be no vertex-sharing between triangles. This keeps the meshes from blurring texture/lighting
elementArrayBufferData.put(incrementer);
incrementer++;
}
//actually buffer vertices
if(vertexArrayBufferData.position() > 0){
@ -858,11 +835,6 @@ public class TerrainChunkModelGeneration {
// SAMPLER INDICES
//
try {
int vertexCount = data.getTextureSamplers().size() / 3;
FloatBuffer samplerBuffer = BufferUtils.createFloatBuffer(vertexCount * 3);
for(float samplerVec : data.getTextureSamplers()){
samplerBuffer.put(samplerVec);
}
if(samplerBuffer.position() > 0){
samplerBuffer.flip();
mesh.bufferCustomFloatAttribArray(samplerBuffer, 3, SAMPLER_INDEX_ATTRIB_LOC);
@ -875,14 +847,9 @@ public class TerrainChunkModelGeneration {
// SAMPLER RATIO DATA
//
try {
int vertexCount = data.getTextureRatioVectors().size() / 3;
FloatBuffer samplerBuffer = BufferUtils.createFloatBuffer(vertexCount * 3);
for(float samplerVec : data.getTextureRatioVectors()){
samplerBuffer.put(samplerVec);
}
if(samplerBuffer.position() > 0){
samplerBuffer.flip();
mesh.bufferCustomFloatAttribArray(samplerBuffer, 3, SAMPLER_RATIO_ATTRIB_LOC);
if(ratioBuffer.position() > 0){
ratioBuffer.flip();
mesh.bufferCustomFloatAttribArray(ratioBuffer, 3, SAMPLER_RATIO_ATTRIB_LOC);
}
} catch (NullPointerException ex){
ex.printStackTrace();
@ -918,7 +885,7 @@ public class TerrainChunkModelGeneration {
*/
public static Model generateTerrainModel(TerrainChunkData data, VoxelTextureAtlas atlas){
Model rVal = new Model();
Mesh m = generateTerrainMesh(data);
Mesh m = TerrainChunkModelGeneration.generateTerrainMesh(data);
//construct the material for the chunk
Material groundMat = new Material();

View File

@ -19,6 +19,7 @@ import electrosphere.entity.Entity;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.ServerEntityUtils;
import electrosphere.entity.state.server.ServerPlayerViewDirTree;
import electrosphere.entity.types.creature.CreatureUtils;
import electrosphere.game.server.world.ServerWorldData;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.EntityMessage;
@ -401,12 +402,16 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
//clear all entities in cell
for(Entity entity : cell.getScene().getEntityList()){
if(ServerPlayerViewDirTree.hasTree(entity)){
int playerId = CreatureUtils.getControllerPlayerId(entity);
Player player = Globals.playerManager.getPlayerFromId(playerId);
throw new Error(
"Trying to unload a player's entity! " +
entity + "\n" +
EntityUtils.getPosition(entity) + "\n" +
serverWorldData.convertRealToWorldSpace(EntityUtils.getPosition(entity)) + "\n" +
worldPos
"entity: " + entity + "\n" +
"entity pos (real): " + EntityUtils.getPosition(entity) + "\n" +
"entity pos (world): " + serverWorldData.convertRealToWorldSpace(EntityUtils.getPosition(entity)) + "\n" +
"chunk pos (world): " + worldPos + "\n" +
"player pos (world): " + player.getWorldPos() + "\n" +
"Number of players in cell: " + cell.getPlayers().size()
);
}
ServerEntityUtils.destroyEntity(entity);

View File

@ -11,6 +11,7 @@ import java.util.concurrent.Semaphore;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterOutputStream;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
@ -153,14 +154,21 @@ public class ChunkDiskMap {
}
IntBuffer intView = buffer.asIntBuffer();
intView.position(DIM * DIM * DIM);
int firstType = -1;
boolean homogenous = true;
for(int x = 0; x < DIM; x++){
for(int y = 0; y < DIM; y++){
for(int z = 0; z < DIM; z++){
values[x][y][z] = intView.get();
if(firstType == -1){
firstType = values[x][y][z];
} else if(homogenous && firstType == values[x][y][z]){
homogenous = false;
}
}
}
}
rVal = new ServerTerrainChunk(worldX, worldY, worldZ, weights, values);
rVal = new ServerTerrainChunk(worldX, worldY, worldZ, homogenous ? firstType : ChunkData.NOT_HOMOGENOUS, weights, values);
}
}
lock.release();

View File

@ -1,5 +1,6 @@
package electrosphere.server.terrain.generation;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.entity.scene.RealmDescriptor;
import electrosphere.server.terrain.generation.interfaces.ChunkGenerator;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
@ -43,7 +44,7 @@ public class DefaultChunkGenerator implements ChunkGenerator {
}
}
}
ServerTerrainChunk rVal = new ServerTerrainChunk(worldX, worldY, worldZ, weights, values);
ServerTerrainChunk rVal = new ServerTerrainChunk(worldX, worldY, worldZ, ChunkData.NOT_HOMOGENOUS, weights, values);
return rVal;
}

View File

@ -3,6 +3,7 @@ package electrosphere.server.terrain.generation;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.game.terrain.processing.TerrainInterpolator;
import electrosphere.server.terrain.generation.interfaces.ChunkGenerator;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
@ -52,7 +53,7 @@ public class OverworldChunkGenerator implements ChunkGenerator {
}
}
}
returnedChunk = new ServerTerrainChunk(worldX, worldY, worldZ, weights, values);
returnedChunk = new ServerTerrainChunk(worldX, worldY, worldZ, ChunkData.NOT_HOMOGENOUS, weights, values);
return returnedChunk;
}

View File

@ -3,6 +3,8 @@ package electrosphere.server.terrain.generation;
import java.util.HashMap;
import java.util.Map;
import electrosphere.client.terrain.cache.ChunkData;
// import org.graalvm.polyglot.Value;
import electrosphere.engine.Globals;
@ -94,6 +96,8 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
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];
int firstType = -1;
boolean homogenous = true;
try {
//actual generation algo
@ -176,6 +180,11 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
weights[x][y][z] = voxel.weight;
values[x][y][z] = voxel.type;
}
if(firstType == -1){
firstType = values[x][y][z];
} else if(homogenous && firstType == values[x][y][z]){
homogenous = false;
}
}
}
Globals.profiler.endCpuSample();
@ -184,7 +193,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
} catch(Exception ex){
ex.printStackTrace();
}
rVal = new ServerTerrainChunk(worldX, worldY, worldZ, weights, values);
rVal = new ServerTerrainChunk(worldX, worldY, worldZ, homogenous ? firstType : ChunkData.NOT_HOMOGENOUS, weights, values);
Globals.profiler.endCpuSample();
return rVal;
}

View File

@ -52,15 +52,21 @@ public class ServerTerrainChunk {
*/
int[][][] values;
/**
* The homogenous value of this data, or ChunkData.NOT_HOMOGENOUS if it is not homogenous
*/
int homogenousValue;
/**
* Constructor
* @param worldX The world position x coordinate
* @param worldY The world position y coordinate
* @param worldZ The world position z coordinate
* @param homogenousValue The homogenous value of the terrain chunk
* @param weights The weights of the chunk
* @param values The values of the chunk
*/
public ServerTerrainChunk(int worldX, int worldY, int worldZ, float[][][] weights, int[][][] values) {
public ServerTerrainChunk(int worldX, int worldY, int worldZ, int homogenousValue, float[][][] weights, int[][][] values) {
this.worldX = worldX;
this.worldY = worldY;
this.worldZ = worldZ;
@ -182,5 +188,12 @@ public class ServerTerrainChunk {
return values[x][y][z];
}
/**
* Gets the homogenous value of the chunk
* @return The homogenous value of this data, or ChunkData.NOT_HOMOGENOUS if it is not homogenous
*/
public int getHomogenousValue(){
return homogenousValue;
}
}

View File

@ -136,7 +136,7 @@ public class WorldOctTree <T> {
FloatingChunkTreeNode<T> newContainer = new FloatingChunkTreeNode<>(this, currentLevel, min, max);
//replace existing node
replaceNode(existing,newContainer);
this.replaceNode(existing,newContainer);
//update tracking
this.nodes.remove(existing);
@ -156,8 +156,9 @@ public class WorldOctTree <T> {
this.root = newNode;
} else {
FloatingChunkTreeNode<T> parent = existing.getParent();
int index = parent.children.indexOf(existing);
parent.removeChild(existing);
parent.addChild(newNode);
parent.addChild(index, newNode);
}
}
@ -422,6 +423,16 @@ public class WorldOctTree <T> {
child.parent = this;
}
/**
* Adds a child to this node
* @param index The index of the child
* @param child The child
*/
private void addChild(int index, FloatingChunkTreeNode<T> child){
this.children.add(index, child);
child.parent = this;
}
/**
* Removes a child node
* @param child the child

View File

@ -1,6 +1,7 @@
package electrosphere.util.math;
import org.joml.Vector3d;
import org.joml.Vector3i;
/**
* Utilities for dealing with geometry
@ -101,5 +102,284 @@ public class GeomUtils {
}
}
}
/**
* Gets the minimum squared distance from a point to an axis aligned cube
* @param pos the position to check against
* @param cubeMin The min position of the cube
* @param cubeMax The max position of the cube
* @return the distance
*/
public static double getMinSquaredDistanceAABB(Vector3d pos, Vector3d cubeMin, Vector3d cubeMax){
double minX = cubeMin.x;
double minY = cubeMin.y;
double minZ = cubeMin.z;
double maxX = cubeMax.x;
double maxY = cubeMax.y;
double maxZ = cubeMax.z;
if(pos.x > minX && pos.x < maxX){
if(pos.y > minY && pos.y < maxY){
if(pos.z > minZ && pos.z < maxZ){
return 0;
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(pos.x,pos.y,minZ);
} else {
return pos.distanceSquared(pos.x,pos.y,maxZ);
}
} else if(Math.abs(pos.y - minY) < Math.abs(pos.y - maxY)){
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(pos.x,minY,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(pos.x,minY,minZ);
} else {
return pos.distanceSquared(pos.x,minY,maxZ);
}
} else {
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(pos.x,maxY,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(pos.x,maxY,minZ);
} else {
return pos.distanceSquared(pos.x,maxY,maxZ);
}
}
} else if(Math.abs(pos.x - minX) < Math.abs(pos.x - maxX)){
if(pos.y > minY && pos.y < maxY){
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(minX,pos.y,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(minX,pos.y,minZ);
} else {
return pos.distanceSquared(minX,pos.y,maxZ);
}
} else if(Math.abs(pos.y - minY) < Math.abs(pos.y - maxY)){
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(minX,minY,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(minX,minY,minZ);
} else {
return pos.distanceSquared(minX,minY,maxZ);
}
} else {
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(minX,maxY,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(minX,maxY,minZ);
} else {
return pos.distanceSquared(minX,maxY,maxZ);
}
}
} else {
if(pos.y > minY && pos.y < maxY){
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(maxX,pos.y,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(maxX,pos.y,minZ);
} else {
return pos.distanceSquared(maxX,pos.y,maxZ);
}
} else if(Math.abs(pos.y - minY) < Math.abs(pos.y - maxY)){
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(maxX,minY,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(maxX,minY,minZ);
} else {
return pos.distanceSquared(maxX,minY,maxZ);
}
} else {
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(maxX,maxY,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(maxX,maxY,minZ);
} else {
return pos.distanceSquared(maxX,maxY,maxZ);
}
}
}
}
/**
* Gets the minimum squared distance from a point to an axis aligned cube
* @param pos the position to check against
* @param cubeMin The min position of the cube
* @param cubeMax The max position of the cube
* @return the distance
*/
public static double getMinSquaredDistanceAABB(Vector3i pos, Vector3i cubeMin, Vector3i cubeMax){
int minX = cubeMin.x;
int minY = cubeMin.y;
int minZ = cubeMin.z;
int maxX = cubeMax.x;
int maxY = cubeMax.y;
int maxZ = cubeMax.z;
if(pos.x > minX && pos.x < maxX){
if(pos.y > minY && pos.y < maxY){
if(pos.z > minZ && pos.z < maxZ){
return 0;
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(pos.x,pos.y,minZ);
} else {
return pos.distanceSquared(pos.x,pos.y,maxZ);
}
} else if(Math.abs(pos.y - minY) < Math.abs(pos.y - maxY)){
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(pos.x,minY,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(pos.x,minY,minZ);
} else {
return pos.distanceSquared(pos.x,minY,maxZ);
}
} else {
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(pos.x,maxY,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(pos.x,maxY,minZ);
} else {
return pos.distanceSquared(pos.x,maxY,maxZ);
}
}
} else if(Math.abs(pos.x - minX) < Math.abs(pos.x - maxX)){
if(pos.y > minY && pos.y < maxY){
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(minX,pos.y,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(minX,pos.y,minZ);
} else {
return pos.distanceSquared(minX,pos.y,maxZ);
}
} else if(Math.abs(pos.y - minY) < Math.abs(pos.y - maxY)){
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(minX,minY,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(minX,minY,minZ);
} else {
return pos.distanceSquared(minX,minY,maxZ);
}
} else {
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(minX,maxY,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(minX,maxY,minZ);
} else {
return pos.distanceSquared(minX,maxY,maxZ);
}
}
} else {
if(pos.y > minY && pos.y < maxY){
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(maxX,pos.y,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(maxX,pos.y,minZ);
} else {
return pos.distanceSquared(maxX,pos.y,maxZ);
}
} else if(Math.abs(pos.y - minY) < Math.abs(pos.y - maxY)){
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(maxX,minY,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(maxX,minY,minZ);
} else {
return pos.distanceSquared(maxX,minY,maxZ);
}
} else {
if(pos.z > minZ && pos.z < maxZ){
return pos.distanceSquared(maxX,maxY,pos.z);
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distanceSquared(maxX,maxY,minZ);
} else {
return pos.distanceSquared(maxX,maxY,maxZ);
}
}
}
}
/**
* Gets the minimum squared distance from a point to an axis aligned cube
* @param pos the position to check against
* @param cubeMin The min position of the cube
* @param cubeMax The max position of the cube
* @return the distance
*/
public static double getMinSquaredDistanceAABBUnrolled(int posX, int posY, int posZ, int minX, int minY, int minZ, int maxX, int maxY, int maxZ){
if(posX > minX && posX < maxX){
if(posY > minY && posY < maxY){
if(posZ > minZ && posZ < maxZ){
return 0;
} else if(Math.abs(posZ - minZ) < Math.abs(posZ - maxZ)){
return (posZ - minZ) * (posZ - minZ);// pos.distanceSquared(posX,posY,minZ);
} else {
return (posZ - maxZ) * (posZ - maxZ);//pos.distanceSquared(posX,posY,maxZ);
}
} else if(Math.abs(posY - minY) < Math.abs(posY - maxY)){
if(posZ > minZ && posZ < maxZ){
return (posY - minY) * (posY - minY);//pos.distanceSquared(posX,minY,posZ);
} else if(Math.abs(posZ - minZ) < Math.abs(posZ - maxZ)){
return (posY - minY) * (posY - minY) + (posZ - minZ) * (posZ - minZ);//pos.distanceSquared(posX,minY,minZ);
} else {
return (posY - minY) * (posY - minY) + (posZ - maxZ) * (posZ - maxZ);//pos.distanceSquared(posX,minY,maxZ);
}
} else {
if(posZ > minZ && posZ < maxZ){
return (posY - maxY) * (posY - maxY);//pos.distanceSquared(posX,maxY,posZ);
} else if(Math.abs(posZ - minZ) < Math.abs(posZ - maxZ)){
return (posY - maxY) * (posY - maxY) + (posZ - minZ) * (posZ - minZ);//pos.distanceSquared(posX,maxY,minZ);
} else {
return (posY - maxY) * (posY - maxY) + (posZ - maxZ) * (posZ - maxZ);//pos.distanceSquared(posX,maxY,maxZ);
}
}
} else if(Math.abs(posX - minX) < Math.abs(posX - maxX)){
if(posY > minY && posY < maxY){
if(posZ > minZ && posZ < maxZ){
return (posX - minX) * (posX - minX);//pos.distanceSquared(minX,posY,posZ);
} else if(Math.abs(posZ - minZ) < Math.abs(posZ - maxZ)){
return (posX - minX) * (posX - minX) + (posZ - minZ) * (posZ - minZ);//pos.distanceSquared(minX,posY,minZ);
} else {
return (posX - minX) * (posX - minX) + (posZ - maxZ) * (posZ - maxZ);//pos.distanceSquared(minX,posY,maxZ);
}
} else if(Math.abs(posY - minY) < Math.abs(posY - maxY)){
if(posZ > minZ && posZ < maxZ){
return (posX - minX) * (posX - minX);//pos.distanceSquared(minX,minY,posZ);
} else if(Math.abs(posZ - minZ) < Math.abs(posZ - maxZ)){
return (posX - minX) * (posX - minX) + (posY - minY) * (posY - minY) + (posZ - minZ) * (posZ - minZ);//pos.distanceSquared(minX,minY,minZ);
} else {
return (posX - minX) * (posX - minX) + (posY - minY) * (posY - minY) + (posZ - maxZ) * (posZ - maxZ);//pos.distanceSquared(minX,minY,maxZ);
}
} else {
if(posZ > minZ && posZ < maxZ){
return (posX - minX) * (posX - minX) + (posY - maxY) * (posY - maxY);//pos.distanceSquared(minX,maxY,posZ);
} else if(Math.abs(posZ - minZ) < Math.abs(posZ - maxZ)){
return (posX - minX) * (posX - minX) + (posY - maxY) * (posY - maxY) + (posZ - minZ) * (posZ - minZ);//pos.distanceSquared(minX,maxY,minZ);
} else {
return (posX - minX) * (posX - minX) + (posY - maxY) * (posY - maxY) + (posZ - maxZ) * (posZ - maxZ);//pos.distanceSquared(minX,maxY,maxZ);
}
}
} else {
if(posY > minY && posY < maxY){
if(posZ > minZ && posZ < maxZ){
return (posX - maxX) * (posX - maxX);//pos.distanceSquared(maxX,posY,posZ);
} else if(Math.abs(posZ - minZ) < Math.abs(posZ - maxZ)){
return (posX - maxX) * (posX - maxX) + (posZ - minZ) * (posZ - minZ);//pos.distanceSquared(maxX,posY,minZ);
} else {
return (posX - maxX) * (posX - maxX) + (posZ - maxZ) * (posZ - maxZ);//pos.distanceSquared(maxX,posY,maxZ);
}
} else if(Math.abs(posY - minY) < Math.abs(posY - maxY)){
if(posZ > minZ && posZ < maxZ){
return (posX - maxX) * (posX - maxX);//pos.distanceSquared(maxX,minY,posZ);
} else if(Math.abs(posZ - minZ) < Math.abs(posZ - maxZ)){
return (posX - maxX) * (posX - maxX) + (posY - minY) * (posY - minY) + (posZ - minZ) * (posZ - minZ);//pos.distanceSquared(maxX,minY,minZ);
} else {
return (posX - maxX) * (posX - maxX) + (posY - minY) * (posY - minY) + (posZ - maxZ) * (posZ - maxZ);//pos.distanceSquared(maxX,minY,maxZ);
}
} else {
if(posZ > minZ && posZ < maxZ){
return (posX - maxX) * (posX - maxX) + (posY - maxY) * (posY - maxY);//pos.distanceSquared(maxX,maxY,posZ);
} else if(Math.abs(posZ - minZ) < Math.abs(posZ - maxZ)){
return (posX - maxX) * (posX - maxX) + (posY - maxY) * (posY - maxY) + (posZ - minZ) * (posZ - minZ);//pos.distanceSquared(maxX,maxY,minZ);
} else {
return (posX - maxX) * (posX - maxX) + (posY - maxY) * (posY - maxY) + (posZ - maxZ) * (posZ - maxZ);//pos.distanceSquared(maxX,maxY,maxZ);
}
}
}
}
}

View File

@ -2,7 +2,6 @@ package electrosphere.client.terrain.cells;
import static org.junit.jupiter.api.Assertions.*;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.joml.Vector3i;
import org.junit.jupiter.api.extension.ExtendWith;
@ -39,11 +38,11 @@ public class ClientDrawCellManagerTests {
Globals.clientWorldData = new ClientWorldData(new Vector3f(0), new Vector3f(worldDiscreteSize * ServerTerrainChunk.CHUNK_DIMENSION), 0, worldDiscreteSize);
ClientDrawCellManager manager = new ClientDrawCellManager(null, 64);
double precomputedMidDist = 0;
Vector3d playerPos = new Vector3d(0,0,0);
Vector3i playerPos = new Vector3i(0,0,0);
FloatingChunkTreeNode<DrawCell> node = FloatingChunkTreeNode.constructorForTests(manager.chunkTree, 1, new Vector3i(16,0,0), new Vector3i(32,16,16));
node.convertToLeaf(DrawCell.generateTerrainCell(new Vector3i(0,0,0)));
node.convertToLeaf(DrawCell.generateTerrainCell(new Vector3i(0,0,0),3));
assertTrue(manager.shouldSplit(playerPos, node));
assertTrue(manager.shouldSplit(playerPos, node, 0));
//cleanup
Main.shutdown();