package electrosphere.client.foliagemanager; import java.util.List; import java.util.LinkedList; import org.joml.Vector3d; import org.joml.Vector3i; import electrosphere.client.terrain.cache.ChunkData; import electrosphere.engine.Globals; import electrosphere.entity.EntityUtils; import electrosphere.util.ds.octree.ChunkTree; import electrosphere.util.ds.octree.ChunkTree.ChunkTreeNode; import electrosphere.util.math.GeomUtils; /** * A whole chunk of foliage */ public class FoliageChunk { /** * The world position of this chunk */ Vector3i worldPos; Vector3d realPos; /** * The distance to draw at full resolution */ static final double FULL_RES_DIST = 20; /** * The distance for half resolution */ static final double HALF_RES_DIST = 40; /** * The octree holding all the chunks to evaluate */ ChunkTree chunkTree; /** * Data for the current chunk */ ChunkData currentChunkData; /** * Data for the above chunk */ ChunkData aboveChunkData; /** * Constructor * @param worldPos The world position of the chunk */ public FoliageChunk(Vector3i worldPos){ this.worldPos = worldPos; this.realPos = Globals.clientWorldData.convertWorldToRealSpace(worldPos); this.chunkTree = new ChunkTree(); } /** * Gets the world position of the chunk * @return The world position of the chunk */ public Vector3i getWorldPos(){ return worldPos; } /** * Initializes all cells in the chunk */ public void initCells(){ 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)); this.updateCells(); 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 boolean updated = true; int attempts = 0; while(updated && attempts < 3){ ChunkTreeNode rootNode = this.chunkTree.getRoot(); updated = this.recursivelyUpdateCells(rootNode, playerPos); attempts++; } Globals.profiler.endCpuSample(); } /** * Recursively update child nodes * @param node The root node * @param playerPos The player's position */ private boolean recursivelyUpdateCells(ChunkTreeNode node, Vector3d playerPos){ boolean updated = false; 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())); }); updated = true; } 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())); updated = true; } else if(shouldGenerate(playerPos, node)){ node.getData().generate(); updated = true; } else if(!node.isLeaf()){ List> children = new LinkedList>(node.getChildren()); for(ChunkTreeNode child : children){ boolean childUpdate = recursivelyUpdateCells(child, playerPos); updated = childUpdate || updated; } } return updated; } /** * Gets the minimum distance from a node to a point * @param pos the position to check against * @param node the node * @return the distance */ public double getMinDistance(Vector3d pos, ChunkTreeNode node){ Vector3i min = node.getMinBound(); Vector3i max = node.getMaxBound(); double minX = min.x + realPos.x; double minY = min.y + realPos.y; double minZ = min.z + realPos.z; double maxX = max.x + realPos.x; double maxY = max.y + realPos.y; double maxZ = max.z + realPos.z; return GeomUtils.getMinDistanceAABB(pos, new Vector3d(minX,minY,minZ), new Vector3d(maxX,maxY,maxZ)); } /** * Gets whether this should be split or not * @param pos the player position * @param node The node * @return true if should split, false otherwise */ 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 node.isLeaf() && node.canSplit() && ( ( node.getLevel() < ChunkTree.MAX_LEVEL - 1 && this.getMinDistance(pos, node) <= HALF_RES_DIST ) || ( node.getLevel() < ChunkTree.MAX_LEVEL && this.getMinDistance(pos, node) <= FULL_RES_DIST ) ) ; } /** * Gets whether this should be joined or not * @param pos the player position * @param node The node * @return true if should be joined, false otherwise */ 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 node.getLevel() > 0 && !node.isLeaf() && ( ( node.getLevel() == ChunkTree.MAX_LEVEL && this.getMinDistance(pos, node) > FULL_RES_DIST ) || ( this.getMinDistance(pos, node) > HALF_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.isLeaf() && node.getData() != null && node.getData().containedEntities.size() < 1 && ( ( node.getLevel() == ChunkTree.MAX_LEVEL // && // this.getMinDistance(pos, node) <= FULL_RES_DIST ) || ( node.getLevel() == ChunkTree.MAX_LEVEL - 1 && this.getMinDistance(pos, node) <= HALF_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(){ 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(); } } /** * Draws all cells in the chunk */ protected void 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(); } } }