Renderer/src/main/java/electrosphere/client/foliagemanager/FoliageChunk.java
2024-09-11 20:54:04 -04:00

298 lines
8.9 KiB
Java

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<FoliageCell> 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<FoliageCell>();
}
/**
* 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<FoliageCell> 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<FoliageCell> node, Vector3d playerPos){
boolean updated = false;
if(this.shouldSplit(playerPos, node)){
//perform op
ChunkTreeNode<FoliageCell> 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<FoliageCell> 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<ChunkTreeNode<FoliageCell>> children = new LinkedList<ChunkTreeNode<FoliageCell>>(node.getChildren());
for(ChunkTreeNode<FoliageCell> 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<FoliageCell> 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<FoliageCell> 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<FoliageCell> 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<FoliageCell> 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<FoliageCell> 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<FoliageCell> 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<FoliageCell> node){
if(node.getChildren().size() > 0){
node.getChildren().forEach(child -> recursivelyDraw(child));
}
if(node.getData() != null){
node.getData().draw();
}
}
}