298 lines
8.9 KiB
Java
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();
|
|
}
|
|
}
|
|
|
|
|
|
}
|