From 843043950965bfb5bc1b6bdcd9f17daf5f140c5f Mon Sep 17 00:00:00 2001 From: austin Date: Wed, 11 Sep 2024 19:02:53 -0400 Subject: [PATCH] foliage manager fixes --- .../foliagemanager/ClientFoliageManager.java | 8 +- .../client/foliagemanager/FoliageCell.java | 10 +- .../client/foliagemanager/FoliageChunk.java | 207 ++++++++++++------ .../client/scene/ClientWorldData.java | 15 ++ src/main/java/electrosphere/engine/Main.java | 4 + .../engine/profiler/Profiler.java | 9 + .../net/client/protocol/TerrainProtocol.java | 2 +- .../util/ds/octree/ChunkTree.java | 15 -- .../util/ds/octree/ChunkTreeTests.java | 10 + 9 files changed, 188 insertions(+), 92 deletions(-) create mode 100644 src/test/java/electrosphere/util/ds/octree/ChunkTreeTests.java diff --git a/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java b/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java index 46e94125..55e61a59 100644 --- a/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java +++ b/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java @@ -61,15 +61,19 @@ public class ClientFoliageManager { for(int x = -chunkRadius; x < chunkRadius+1; x++){ for(int y = -chunkRadius; y < chunkRadius+1; y++){ for(int z = -chunkRadius; z < chunkRadius+1; z++){ - Vector3i worldPos = Globals.clientWorldData.convertRealToWorldSpace(EntityUtils.getPosition(Globals.playerEntity)); - updatePosition(worldPos); + Vector3i worldPos = Globals.clientWorldData.convertRealToWorldSpace(EntityUtils.getPosition(Globals.playerEntity)).add(x,y,z); + if(Globals.clientWorldData.worldPosInBounds(worldPos)){ + this.updatePosition(worldPos); + } } } } + Globals.profiler.beginCpuSample("ClientFoliageManager.update - destroy chunks"); for(FoliageChunk chunk : this.chunkUpdateCache){ chunk.destroy(); } this.chunkUpdateCache.clear(); + Globals.profiler.endCpuSample(); } Globals.profiler.endCpuSample(); } diff --git a/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java b/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java index 658aecb3..49a40a4f 100644 --- a/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java +++ b/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java @@ -22,8 +22,6 @@ import electrosphere.client.terrain.cache.ChunkData; import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.EntityCreationUtils; -import electrosphere.entity.EntityDataStrings; -import electrosphere.entity.EntityTags; import electrosphere.entity.EntityUtils; import electrosphere.entity.state.foliage.AmbientFoliage; import electrosphere.entity.types.camera.CameraEntityUtils; @@ -344,7 +342,7 @@ public class FoliageCell { EntityUtils.getRotation(grassEntity).set(0,0,0,1); EntityUtils.getScale(grassEntity).set(1,1,1); //add ambient foliage behavior tree - AmbientFoliage.attachAmbientFoliageTree(grassEntity, 0.0f, foliageType.getGrowthModel().getGrowthRate()); + AmbientFoliage.attachAmbientFoliageTree(grassEntity, 1.0f, foliageType.getGrowthModel().getGrowthRate()); this.addEntity(grassEntity); } } @@ -384,9 +382,9 @@ public class FoliageCell { RenderPipelineState renderPipelineState = Globals.renderingEngine.getRenderPipelineState(); OpenGLState openGLState = Globals.renderingEngine.getOpenGLState(); - Vector3f cameraModifiedPosition = new Vector3f((float)realPosition.x,(float)realPosition.y,(float)realPosition.z).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera)); + Vector3f cameraModifiedPosition = new Vector3f((float)realPosition.x,(float)realPosition.y,(float)realPosition.z).sub(cameraCenter); //frustum check entire cell - boolean shouldRender = true;//renderPipelineState.getFrustumIntersection().testSphere((float)(cameraModifiedPosition.x + boundingSphere.x), (float)(cameraModifiedPosition.y + boundingSphere.y), (float)(cameraModifiedPosition.z + boundingSphere.z), (float)(boundingSphere.r)); + boolean shouldRender = renderPipelineState.getFrustumIntersection().testSphere((float)(cameraModifiedPosition.x + boundingSphere.x), (float)(cameraModifiedPosition.y + boundingSphere.y), (float)(cameraModifiedPosition.z + boundingSphere.z), (float)(boundingSphere.r)); if(shouldRender){ //disable frustum check and instead perform at cell level boolean currentFrustumCheckState = renderPipelineState.shouldFrustumCheck(); @@ -398,7 +396,7 @@ public class FoliageCell { modelMatrix = modelMatrix.identity(); - cameraModifiedPosition = new Vector3f((float)grassPosition.x,(float)grassPosition.y,(float)grassPosition.z).sub(cameraCenter); + // cameraModifiedPosition = new Vector3f((float)grassPosition.x,(float)grassPosition.y,(float)grassPosition.z).sub(cameraCenter); modelMatrix.translate(cameraModifiedPosition); modelMatrix.rotate(new Quaterniond(grassRotation)); modelMatrix.scale(new Vector3d(EntityUtils.getScale(entity))); diff --git a/src/main/java/electrosphere/client/foliagemanager/FoliageChunk.java b/src/main/java/electrosphere/client/foliagemanager/FoliageChunk.java index a7e94614..80e476e1 100644 --- a/src/main/java/electrosphere/client/foliagemanager/FoliageChunk.java +++ b/src/main/java/electrosphere/client/foliagemanager/FoliageChunk.java @@ -1,7 +1,6 @@ package electrosphere.client.foliagemanager; import java.util.LinkedList; -import java.util.List; import org.joml.Vector3d; import org.joml.Vector3i; @@ -65,80 +64,100 @@ public class FoliageChunk { * Initializes all cells in the chunk */ public void initCells(){ - Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity); + Globals.profiler.beginCpuSample("FoliageChunk.initCells"); this.currentChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos); // //evaluate top cells if chunk above this one exists this.aboveChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(worldPos).add(0,1,0)); - - //the sets to iterate through - ChunkTreeNode rootNode = this.chunkTree.getRoot(); - List> openSet = new LinkedList>(); - List> toInit = new LinkedList>(); - openSet.add(rootNode); - - //split into nodes - while(openSet.size() > 0){ - ChunkTreeNode current = openSet.remove(0); - if(this.shouldSplit(playerPos, current)){ - //add children for this one - ChunkTreeNode container = chunkTree.split(current); - openSet.addAll(container.getChildren()); - } else { - //do nothing - toInit.add(current); - } - } - //init end-nodes as leaves - for(ChunkTreeNode current : toInit){ - Vector3d realPos = Globals.clientWorldData.convertWorldToRealSpace(worldPos).add(new Vector3d(current.getMinBound())); - current.convertToLeaf(new FoliageCell(worldPos, current.getMinBound(), realPos, 4 - current.getLevel(), 1.0f)); - current.getData().generate(); - } + Globals.profiler.endCpuSample(); } /** * Updates all cells in the chunk */ public void updateCells(){ + Globals.profiler.beginCpuSample("FoliageChunk.updateCells"); Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity); //the sets to iterate through ChunkTreeNode rootNode = this.chunkTree.getRoot(); - List> openSet = new LinkedList>(); - List> toInit = new LinkedList>(); - List> toCleanup = new LinkedList>(); - openSet.add(rootNode); + this.recursivelyUpdateCells(rootNode, playerPos); //split into nodes - while(openSet.size() > 0){ - ChunkTreeNode current = openSet.remove(0); + // Globals.profiler.beginCpuSample("FoliageChunk.updateCells - evaluate split/join"); + // while(openSet.size() > 0){ + // ChunkTreeNode current = openSet.remove(0); - if(this.shouldSplit(playerPos, current)){ - //add children for this one - ChunkTreeNode container = chunkTree.split(current); - toInit.addAll(container.getChildren()); - toCleanup.add(current); - } else if(this.shouldJoin(playerPos, current)) { - //add children for this one - ChunkTreeNode newLeaf = chunkTree.join(current); - toCleanup.addAll(current.getChildren()); - toCleanup.add(current); - toInit.add(newLeaf); - } else if(!current.isLeaf()){ - openSet.addAll(current.getChildren()); - } + // if(this.shouldSplit(playerPos, current)){ + // //add children for this one + // ChunkTreeNode container = chunkTree.split(current); + // toInit.addAll(container.getChildren()); + // toCleanup.add(current); + // } else if(this.shouldJoin(playerPos, current)) { + // //add children for this one + // ChunkTreeNode newLeaf = chunkTree.join(current); + // toCleanup.addAll(current.getChildren()); + // toCleanup.add(current); + // toInit.add(newLeaf); + // } else if(!current.isLeaf()){ + // openSet.addAll(current.getChildren()); + // } + // } + // Globals.profiler.endCpuSample(); + // Globals.profiler.beginCpuSample("FoliageChunk.updateCells - generate"); + // //init end-nodes as leaves + // for(ChunkTreeNode current : toInit){ + // Vector3d realPos = Globals.clientWorldData.convertWorldToRealSpace(worldPos).add(new Vector3d(current.getMinBound())); + // current.convertToLeaf(new FoliageCell(worldPos, current.getMinBound(), realPos, 5 - current.getLevel(), 1.0f)); + // if(this.shouldGenerate(playerPos,current)){ + // current.getData().generate(); + // } + // } + // Globals.profiler.endCpuSample(); + // Globals.profiler.beginCpuSample("FoliageChunk.updateCells - destroy"); + // //destroy orphan nodes + // for(ChunkTreeNode current : toCleanup){ + // if(current.getData() != null){ + // current.getData().destroy(); + // } + // } + // Globals.profiler.endCpuSample(); + Globals.profiler.endCpuSample(); + } + + /** + * Recursively update child nodes + * @param node The root node + * @param playerPos The player's position + */ + private void recursivelyUpdateCells(ChunkTreeNode node, Vector3d playerPos){ + if(this.shouldSplit(playerPos, node)){ + //perform op + ChunkTreeNode container = chunkTree.split(node); + + //do deletions + this.recursivelyDestroy(node); + + //do creations + container.getChildren().forEach(child -> { + Vector3d realPos = new Vector3d( + worldPos.x * ChunkData.CHUNK_SIZE + child.getMinBound().x, + worldPos.y * ChunkData.CHUNK_SIZE + child.getMinBound().y, + worldPos.z * ChunkData.CHUNK_SIZE + child.getMinBound().z + ); + child.convertToLeaf(new FoliageCell(worldPos, child.getMinBound(), realPos, 5 - child.getLevel(), 1.0f)); + }); + } else if(this.shouldJoin(playerPos, node)) { + //perform op + ChunkTreeNode newLeaf = chunkTree.join(node); + + //do deletions + this.recursivelyDestroy(node); + + //do creations + newLeaf.convertToLeaf(new FoliageCell(worldPos, newLeaf.getMinBound(), realPos, 5 - newLeaf.getLevel(), 1.0f)); + } else if(shouldGenerate(playerPos, node)){ + node.getData().generate(); + } else if(!node.isLeaf()){ + new LinkedList<>(node.getChildren()).forEach(child -> recursivelyUpdateCells(child, playerPos)); } - //init end-nodes as leaves - for(ChunkTreeNode current : toInit){ - Vector3d realPos = Globals.clientWorldData.convertWorldToRealSpace(worldPos).add(new Vector3d(current.getMinBound())); - current.convertToLeaf(new FoliageCell(worldPos, current.getMinBound(), realPos, 5 - current.getLevel(), 1.0f)); - current.getData().generate(); - } - //destroy orphan nodes - for(ChunkTreeNode current : toCleanup){ - if(current.getData() != null){ - current.getData().destroy(); - } - } - } /** @@ -196,9 +215,11 @@ public class FoliageChunk { public boolean shouldSplit(Vector3d pos, ChunkTreeNode node){ //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 this.getMinDistance(pos, node) <= FULL_RES_DIST && + return node.isLeaf() && - node.canSplit() + node.getLevel() < ChunkTree.MAX_LEVEL && + node.canSplit() && + this.getMinDistance(pos, node) <= FULL_RES_DIST ; } @@ -211,17 +232,56 @@ public class FoliageChunk { public boolean shouldJoin(Vector3d pos, ChunkTreeNode node){ //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 this.getMinDistance(pos, node) > FULL_RES_DIST && - !node.isLeaf() + return + node.getLevel() > 0 && + !node.isLeaf() && + this.getMinDistance(pos, node) > FULL_RES_DIST ; } + /** + * Checks if this cell should generate + * @param pos the player's position + * @param node the node + * @return true if should generate, false otherwise + */ + public boolean shouldGenerate(Vector3d pos, ChunkTreeNode node){ + return + node.getLevel() == ChunkTree.MAX_LEVEL && + node.isLeaf() && + node.getData() != null && + node.getData().containedEntities.size() < 1 && + this.getMinDistance(pos, node) <= FULL_RES_DIST; + } + + /** + * Checks if the node should have destroy called on it + * @param node The node + * @return true if should destroy, false otherwise + */ + public boolean shouldDestroy(ChunkTreeNode node){ + return + node.getData() != null && + node.getData().containedEntities.size() > 0; + } + /** * Destroys the foliage chunk */ protected void destroy(){ - for(ChunkTreeNode leaf : this.chunkTree.getLeaves()){ - leaf.getData().destroy(); + this.recursivelyDestroy(this.chunkTree.getRoot()); + } + + /** + * Recursively destroy a tree + * @param node The root of the tree + */ + private void recursivelyDestroy(ChunkTreeNode node){ + if(node.getChildren().size() > 0){ + node.getChildren().forEach(child -> recursivelyDestroy(child)); + } + if(node.getData() != null){ + node.getData().destroy(); } } @@ -229,8 +289,19 @@ public class FoliageChunk { * Draws all cells in the chunk */ protected void draw(){ - for(ChunkTreeNode leaf : this.chunkTree.getLeaves()){ - leaf.getData().draw(); + recursivelyDraw(this.chunkTree.getRoot()); + } + + /** + * Recursively draws all nodes + * @param node The root node + */ + private void recursivelyDraw(ChunkTreeNode node){ + if(node.getChildren().size() > 0){ + node.getChildren().forEach(child -> recursivelyDraw(child)); + } + if(node.getData() != null){ + node.getData().draw(); } } diff --git a/src/main/java/electrosphere/client/scene/ClientWorldData.java b/src/main/java/electrosphere/client/scene/ClientWorldData.java index 1339cade..3b579b78 100644 --- a/src/main/java/electrosphere/client/scene/ClientWorldData.java +++ b/src/main/java/electrosphere/client/scene/ClientWorldData.java @@ -93,6 +93,21 @@ public class ClientWorldData { return convertChunkToRealSpace(world); } + /** + * Checks if a world position is in bounds or not + * @param worldPos The world position + * @return true if is in bounds, false otherwise + */ + public boolean worldPosInBounds(Vector3i worldPos){ + return worldPos.x >= convertRealToWorld(worldMinPoint.x) && + worldPos.x < convertRealToWorld(worldMaxPoint.x) && + worldPos.y >= convertRealToWorld(worldMinPoint.y) && + worldPos.y < convertRealToWorld(worldMaxPoint.y) && + worldPos.z >= convertRealToWorld(worldMinPoint.z) && + worldPos.z < convertRealToWorld(worldMaxPoint.z) + ; + } + /** * Converts a real space position to its world space equivalent * @param position The real space position diff --git a/src/main/java/electrosphere/engine/Main.java b/src/main/java/electrosphere/engine/Main.java index ea9edc15..a8121446 100644 --- a/src/main/java/electrosphere/engine/Main.java +++ b/src/main/java/electrosphere/engine/Main.java @@ -447,6 +447,10 @@ public class Main { if(Globals.netMonitor != null){ Globals.netMonitor.close(); } + //shutdown profiler + if(Globals.profiler != null){ + Globals.profiler.destroy(); + } //shutdown ode if(initOde){ OdeHelper.closeODE(); diff --git a/src/main/java/electrosphere/engine/profiler/Profiler.java b/src/main/java/electrosphere/engine/profiler/Profiler.java index 3d4822f2..d9533338 100644 --- a/src/main/java/electrosphere/engine/profiler/Profiler.java +++ b/src/main/java/electrosphere/engine/profiler/Profiler.java @@ -69,4 +69,13 @@ public class Profiler { } } + /** + * Destroys the profiler + */ + public void destroy(){ + if(PROFILE){ + Remotery.rmt_DestroyGlobalInstance(pointer); + } + } + } diff --git a/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java b/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java index 035d474f..55b43581 100644 --- a/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java +++ b/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java @@ -29,7 +29,7 @@ public class TerrainProtocol implements ClientProtocolTemplate { Globals.clientWorldData = new ClientWorldData( //Vector3f worldMinPoint, Vector3f worldMaxPoint, int dynamicInterpolationRatio, float randomDampener, int worldDiscreteSize new Vector3f(message.getworldMinX(),0,message.getworldMinY()), - new Vector3f(message.getworldMaxX(),3,message.getworldMaxY()), + new Vector3f(message.getworldMaxX(),32,message.getworldMaxY()), ChunkData.CHUNK_SIZE, message.getrandomDampener(), message.getworldSizeDiscrete() diff --git a/src/main/java/electrosphere/util/ds/octree/ChunkTree.java b/src/main/java/electrosphere/util/ds/octree/ChunkTree.java index b133d9d6..06c22115 100644 --- a/src/main/java/electrosphere/util/ds/octree/ChunkTree.java +++ b/src/main/java/electrosphere/util/ds/octree/ChunkTree.java @@ -126,21 +126,6 @@ public class ChunkTree { return this.root; } - /** - * Gets the number of leaves in the tree - */ - public int getNumLeaves() { - return this.getLeaves().size(); - } - - /** - * Gets all leaf nodes - * @return All leaf nodes - */ - public List> getLeaves(){ - return this.nodes.stream().filter(node -> node.isLeaf()).collect(Collectors.toList()); - } - /** * A node in a chunk tree diff --git a/src/test/java/electrosphere/util/ds/octree/ChunkTreeTests.java b/src/test/java/electrosphere/util/ds/octree/ChunkTreeTests.java new file mode 100644 index 00000000..d592588a --- /dev/null +++ b/src/test/java/electrosphere/util/ds/octree/ChunkTreeTests.java @@ -0,0 +1,10 @@ +package electrosphere.util.ds.octree; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit testing for the chunk octree implementation + */ +public class ChunkTreeTests { + +}