From 1b5d6c1795ece350b3809964219190dab3db5068 Mon Sep 17 00:00:00 2001 From: austin Date: Mon, 11 Nov 2024 10:23:49 -0500 Subject: [PATCH] Terrain optimizations --- buildNumber.properties | 4 +- docs/src/progress/renderertodo.md | 11 + net/terrain.json | 6 + .../terrain/cells/ClientDrawCellManager.java | 406 +++++++++++------- .../client/terrain/cells/DrawCell.java | 99 ++++- .../client/terrain/cells/DrawCellManager.java | 3 +- .../terrain/manager/ClientTerrainManager.java | 2 +- .../ui/menu/debug/ImGuiChunkMonitor.java | 2 +- .../editor/ClientEditorMovementTree.java | 2 +- .../entity/types/terrain/TerrainChunk.java | 1 + .../types/terrain/TerrainChunkData.java | 83 ++++ .../parser/net/message/TerrainMessage.java | 44 +- .../net/server/protocol/TerrainProtocol.java | 2 +- .../meshgen/TerrainChunkModelGeneration.java | 53 +-- .../datacell/GriddedDataCellManager.java | 13 +- .../server/terrain/diskmap/ChunkDiskMap.java | 10 +- .../generation/DefaultChunkGenerator.java | 3 +- .../generation/OverworldChunkGenerator.java | 3 +- .../TestGenerationChunkGenerator.java | 11 +- .../terrain/manager/ServerTerrainChunk.java | 15 +- .../util/ds/octree/WorldOctTree.java | 15 +- .../electrosphere/util/math/GeomUtils.java | 280 ++++++++++++ .../cells/ClientDrawCellManagerTests.java | 7 +- 23 files changed, 847 insertions(+), 228 deletions(-) diff --git a/buildNumber.properties b/buildNumber.properties index efc755ef..38e2d8f5 100644 --- a/buildNumber.properties +++ b/buildNumber.properties @@ -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 diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 324f9e19..e30669b5 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -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 diff --git a/net/terrain.json b/net/terrain.json index bd70d2c8..4fc87611 100644 --- a/net/terrain.json +++ b/net/terrain.json @@ -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" ] }, diff --git a/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java b/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java index 3185ee51..fa90f5ab 100644 --- a/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java +++ b/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java @@ -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,Boolean> evaluationMap = new HashMap,Boolean>(); /** - * All draw cells currently tracked + * The last recorded player world position */ - List activeCells = new LinkedList(); + 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 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 node, Vector3d playerPos, Map,Boolean> evaluationMap, int minLeafLod){ - Vector3d playerRealPos = EntityUtils.getPosition(Globals.playerEntity); + private boolean recursivelyUpdateCells(FloatingChunkTreeNode node, Vector3i playerPos, Map,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 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 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 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 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 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 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> children = node.getChildren(); - for(int i = 0; i < 8; i++){ - FloatingChunkTreeNode 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 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 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> children = node.getChildren(); + for(int i = 0; i < 8; i++){ + FloatingChunkTreeNode 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 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 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 node){ + public boolean shouldSplit(Vector3i pos, FloatingChunkTreeNode 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 node){ + private int getLODLevel(FloatingChunkTreeNode 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 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 faces = new LinkedList(); @@ -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 node){ + public boolean shouldJoin(Vector3i pos, FloatingChunkTreeNode 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 node, int minLeafLod){ + public boolean shouldRequest(Vector3i pos, FloatingChunkTreeNode 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 node, int minLeafLod){ + public boolean shouldGenerate(Vector3i pos, FloatingChunkTreeNode 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 node){ + if(node.getData() == null){ + for(FloatingChunkTreeNode child : node.getChildren()){ + if(child.getData() != null){ + child.getData().destroy(); + } + } + } else { node.getData().destroy(); } } diff --git a/src/main/java/electrosphere/client/terrain/cells/DrawCell.java b/src/main/java/electrosphere/client/terrain/cells/DrawCell.java index 4927e68f..42ebe6b4 100644 --- a/src/main/java/electrosphere/client/terrain/cells/DrawCell.java +++ b/src/main/java/electrosphere/client/terrain/cells/DrawCell.java @@ -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 node, int distCache){ + if(cachedMinDistance != INVALID_DIST_CACHE && distCache < lod){ + return cachedMinDistance; + } else { + this.cachedMinDistance = GeomUtils.getMinSquaredDistanceAABB(worldPos, node.getMinBound(), node.getMaxBound()); + return cachedMinDistance; + } + } } diff --git a/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java b/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java index 0d514141..dc102a12 100644 --- a/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java +++ b/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java @@ -233,7 +233,8 @@ public class DrawCellManager { //build the cell DrawCell cell = DrawCell.generateTerrainCell( - worldPos + worldPos, + 0 ); cells.add(cell); keyCellMap.put(targetKey,cell); diff --git a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java index e744bff4..5c3a1801 100644 --- a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java +++ b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java @@ -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 diff --git a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiChunkMonitor.java b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiChunkMonitor.java index 031bae7b..a375fd6a 100644 --- a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiChunkMonitor.java +++ b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiChunkMonitor.java @@ -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()); diff --git a/src/main/java/electrosphere/entity/state/movement/editor/ClientEditorMovementTree.java b/src/main/java/electrosphere/entity/state/movement/editor/ClientEditorMovementTree.java index 4c627389..bf6112ee 100644 --- a/src/main/java/electrosphere/entity/state/movement/editor/ClientEditorMovementTree.java +++ b/src/main/java/electrosphere/entity/state/movement/editor/ClientEditorMovementTree.java @@ -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; diff --git a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java index 893c7c77..f73edbd8 100644 --- a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java +++ b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java @@ -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); diff --git a/src/main/java/electrosphere/entity/types/terrain/TerrainChunkData.java b/src/main/java/electrosphere/entity/types/terrain/TerrainChunkData.java index b997e8d9..b9e76c7c 100644 --- a/src/main/java/electrosphere/entity/types/terrain/TerrainChunkData.java +++ b/src/main/java/electrosphere/entity/types/terrain/TerrainChunkData.java @@ -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 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; + } + + } diff --git a/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java b/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java index 8a21fc6f..2c688cfd 100644 --- a/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java @@ -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: diff --git a/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java b/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java index 5f133ada..275b1cf7 100644 --- a/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java +++ b/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java @@ -246,7 +246,7 @@ public class TerrainProtocol implements ServerProtocolTemplate { // 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 diff --git a/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java b/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java index 573be002..dfab3546 100644 --- a/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java +++ b/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java @@ -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(); diff --git a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java index 32dddab7..fad7aecd 100644 --- a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java +++ b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java @@ -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); diff --git a/src/main/java/electrosphere/server/terrain/diskmap/ChunkDiskMap.java b/src/main/java/electrosphere/server/terrain/diskmap/ChunkDiskMap.java index 037b8469..c593c906 100644 --- a/src/main/java/electrosphere/server/terrain/diskmap/ChunkDiskMap.java +++ b/src/main/java/electrosphere/server/terrain/diskmap/ChunkDiskMap.java @@ -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(); diff --git a/src/main/java/electrosphere/server/terrain/generation/DefaultChunkGenerator.java b/src/main/java/electrosphere/server/terrain/generation/DefaultChunkGenerator.java index 9bab74c0..84f1abbd 100644 --- a/src/main/java/electrosphere/server/terrain/generation/DefaultChunkGenerator.java +++ b/src/main/java/electrosphere/server/terrain/generation/DefaultChunkGenerator.java @@ -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; } diff --git a/src/main/java/electrosphere/server/terrain/generation/OverworldChunkGenerator.java b/src/main/java/electrosphere/server/terrain/generation/OverworldChunkGenerator.java index 09721e00..91ac4d17 100644 --- a/src/main/java/electrosphere/server/terrain/generation/OverworldChunkGenerator.java +++ b/src/main/java/electrosphere/server/terrain/generation/OverworldChunkGenerator.java @@ -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; } diff --git a/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java b/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java index 8064ff26..5794db42 100644 --- a/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java +++ b/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java @@ -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; } diff --git a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainChunk.java b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainChunk.java index 0c59f5ba..1aedbc5d 100644 --- a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainChunk.java +++ b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainChunk.java @@ -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; + } } diff --git a/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java b/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java index d0a25519..7fdac432 100644 --- a/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java +++ b/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java @@ -136,7 +136,7 @@ public class WorldOctTree { FloatingChunkTreeNode 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 { this.root = newNode; } else { FloatingChunkTreeNode 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 { 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 child){ + this.children.add(index, child); + child.parent = this; + } + /** * Removes a child node * @param child the child diff --git a/src/main/java/electrosphere/util/math/GeomUtils.java b/src/main/java/electrosphere/util/math/GeomUtils.java index 0e15e278..a6f218f5 100644 --- a/src/main/java/electrosphere/util/math/GeomUtils.java +++ b/src/main/java/electrosphere/util/math/GeomUtils.java @@ -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); + } + } + } + } } diff --git a/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java b/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java index deb68750..556bae5c 100644 --- a/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java +++ b/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java @@ -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 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();