Voxel system improvements
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2024-11-17 15:40:59 -05:00
parent 596b0f6624
commit a44255060d
9 changed files with 150 additions and 31 deletions

View File

@ -1061,6 +1061,10 @@ Fix hitbox destruction logic to not double-delete
(11/17/2024)
Mountain generation work
Fix flickering chunks on unload
Fix draw cell distance cache busting on far distances
Fix skybox not updating position with player entity
Add Scene shorthand for registering runnables as behavior trees

View File

@ -209,7 +209,8 @@ public class ClientDrawCellManager {
Globals.profiler.beginCpuSample("ClientDrawCellManager.split");
//perform op
WorldOctTreeNode<DrawCell> container = chunkTree.split(node);
container.setData(DrawCell.generateTerrainCell(container.getMinBound(), this.chunkTree.getMaxLevel() - container.getLevel()));
DrawCell containerCell = DrawCell.generateTerrainCell(container.getMinBound(), this.chunkTree.getMaxLevel() - container.getLevel());
container.setData(containerCell);
container.getData().transferChunkData(node.getData());
//do creations
@ -220,6 +221,7 @@ public class ClientDrawCellManager {
child.getMinBound().z
);
DrawCell drawCell = DrawCell.generateTerrainCell(cellWorldPos,this.chunkTree.getMaxLevel() - child.getLevel());
drawCell.registerNotificationTarget(node.getData());
child.setLeaf(true);
child.setData(drawCell);
evaluationMap.put(child,true);
@ -233,13 +235,13 @@ public class ClientDrawCellManager {
Globals.profiler.endCpuSample();
updated = true;
} else if(shouldRequest(playerPos, node, minLeafLod, distCache)){
} else if(this.shouldRequest(playerPos, node, minLeafLod, distCache)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.request");
//calculate what to request
DrawCell cell = node.getData();
List<DrawCellFace> highResFaces = null;
if(shouldSolveFaces(node,playerPos, distCache)){
if(this.shouldSolveFaces(node,playerPos, distCache)){
highResFaces = this.solveHighResFace(node);
}
@ -251,17 +253,17 @@ public class ClientDrawCellManager {
Globals.profiler.endCpuSample();
updated = true;
} else if(shouldGenerate(playerPos, node, minLeafLod, distCache)){
} else if(this.shouldGenerate(playerPos, node, minLeafLod, distCache)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.generate");
int lodLevel = this.getLODLevel(node);
//high res faces
List<DrawCellFace> highResFaces = null;
if(shouldSolveFaces(node,playerPos, distCache)){
if(this.shouldSolveFaces(node,playerPos, distCache)){
highResFaces = this.solveHighResFace(node);
}
if(containsDataToGenerate(node,highResFaces)){
if(this.containsDataToGenerate(node,highResFaces)){
node.getData().generateDrawableEntity(textureAtlas, lodLevel, highResFaces);
if(node.getData().getFailedGenerationAttempts() > FAILED_GENERATION_ATTEMPT_THRESHOLD){
node.getData().setHasRequested(false);
@ -284,21 +286,28 @@ public class ClientDrawCellManager {
this.validCellCount++;
List<WorldOctTreeNode<DrawCell>> children = node.getChildren();
boolean isHomogenous = true;
boolean fullyGenerated = true;
for(int i = 0; i < 8; i++){
WorldOctTreeNode<DrawCell> child = children.get(i);
boolean childUpdate = recursivelyUpdateCells(child, playerPos, evaluationMap, minLeafLod, distCache);
boolean childUpdate = this.recursivelyUpdateCells(child, playerPos, evaluationMap, minLeafLod, distCache);
if(childUpdate == true){
updated = true;
}
if(!child.getData().hasGenerated() || !child.getData().isHomogenous()){
if(!child.getData().hasGenerated()){
fullyGenerated = false;
}
if(!child.getData().isHomogenous()){
isHomogenous = false;
}
}
WorldOctTreeNode<DrawCell> newNode = null;
if(isHomogenous){
WorldOctTreeNode<DrawCell> newNode = this.join(node);
newNode.getData().setHasGenerated(true);
newNode = this.join(node);
newNode.getData().setHomogenous(true);
}
if(fullyGenerated && newNode != null){
newNode.getData().setHasGenerated(true);
}
if((this.chunkTree.getMaxLevel() - node.getLevel()) < minLeafLod){
evaluationMap.put(node,true);
}
@ -331,12 +340,17 @@ public class ClientDrawCellManager {
if(
lastPlayerPos.x / 16 != currentPlayerPos.x / 16 || lastPlayerPos.z / 16 != currentPlayerPos.z / 16 || lastPlayerPos.z / 16 != currentPlayerPos.z / 16
){
return SIXTEENTH_RES_LOD;
return this.chunkTree.getMaxLevel();
}
if(
lastPlayerPos.x / 16 != currentPlayerPos.x / 16 || lastPlayerPos.z / 16 != currentPlayerPos.z / 16 || lastPlayerPos.z / 16 != currentPlayerPos.z / 16
){
return SIXTEENTH_RES_LOD + 2;
}
if(
lastPlayerPos.x / 8 != currentPlayerPos.x / 8 || lastPlayerPos.z / 8 != currentPlayerPos.z / 8 || lastPlayerPos.z / 8 != currentPlayerPos.z / 8
){
return EIGHTH_RES_LOD;
return SIXTEENTH_RES_LOD + 1;
}
if(
lastPlayerPos.x / 4 != currentPlayerPos.x / 4 || lastPlayerPos.z / 4 != currentPlayerPos.z / 4 || lastPlayerPos.z / 4 != currentPlayerPos.z / 4
@ -593,12 +607,14 @@ public class ClientDrawCellManager {
*/
private WorldOctTreeNode<DrawCell> join(WorldOctTreeNode<DrawCell> node){
Globals.profiler.beginCpuSample("ClientDrawCellManager.join");
//perform op
WorldOctTreeNode<DrawCell> newLeaf = chunkTree.join(node, DrawCell.generateTerrainCell(node.getMinBound(),node.getData().lod));
newLeaf.getData().transferChunkData(node.getData());
//do deletions
this.twoLayerDestroy(node);
//queue destructions prior to join -- the join operator clears all children on node
this.recursivelyDestroy(node);
//perform op
DrawCell newLeafCell = DrawCell.generateTerrainCell(node.getMinBound(),node.getData().lod);
WorldOctTreeNode<DrawCell> newLeaf = chunkTree.join(node, newLeafCell);
newLeaf.getData().transferChunkData(node.getData());
//update neighbors
this.conditionalUpdateAdjacentNodes(newLeaf, newLeaf.getLevel());
@ -663,7 +679,6 @@ public class ClientDrawCellManager {
*/
public boolean shouldGenerate(Vector3i pos, WorldOctTreeNode<DrawCell> node, int minLeafLod, int distCache){
return
node.getData() != null &&
!node.getData().hasGenerated() &&
(this.chunkTree.getMaxLevel() - node.getLevel()) <= minLeafLod &&
(
@ -725,7 +740,10 @@ public class ClientDrawCellManager {
*/
private void recursivelyDestroy(WorldOctTreeNode<DrawCell> node){
if(node.getChildren().size() > 0){
node.getChildren().forEach(child -> recursivelyDestroy(child));
for(WorldOctTreeNode<DrawCell> child : node.getChildren()){
child.getData().destroy();
}
// node.getChildren().forEach(child -> recursivelyDestroy(child));
}
if(node.getData() != null){
node.getData().destroy();

View File

@ -7,7 +7,6 @@ import org.joml.Vector3d;
import org.joml.Vector3i;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.collision.CollisionEngine;
import electrosphere.engine.Globals;
import electrosphere.entity.ClientEntityUtils;
import electrosphere.entity.Entity;
@ -88,6 +87,16 @@ public class DrawCell {
* The cached minimum distance
*/
double cachedMinDistance = -1;
/**
* Target to notify on generation completion
*/
DrawCell notifyTarget = null;
/**
* The number of cells that have alerted this one
*/
int generationAlertCount = 0;
/**
@ -161,10 +170,14 @@ public class DrawCell {
}
}
}
if(modelEntity != null){
ClientEntityUtils.destroyEntity(modelEntity);
}
modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(this.chunkData, lod, atlas, this.hasPolygons());
modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(
this.chunkData,
this.notifyTarget,
this.modelEntity,
lod,
atlas,
this.hasPolygons()
);
ClientEntityUtils.initiallyPositionEntity(modelEntity, this.getRealPos(), new Quaterniond());
this.setHasGenerated(true);
}
@ -188,6 +201,24 @@ public class DrawCell {
protected Vector3i getWorldPos(){
return new Vector3i(worldPos);
}
/**
* Registers a target draw cell to notify once this one has completed generating its model
* @param notifyTarget The target to notify
*/
public void registerNotificationTarget(DrawCell notifyTarget){
this.notifyTarget = notifyTarget;
}
/**
* Alerts this draw cell that a child it is waiting on has generated
*/
public void alertToGeneration(){
this.generationAlertCount++;
if(this.generationAlertCount > 8){
this.destroy();
}
}
/**
* Destroys a drawcell including its physics
@ -200,8 +231,6 @@ public class DrawCell {
if(framesSimulated < FRAMES_TO_WAIT_BEFORE_DESTRUCTION){
framesSimulated++;
} else {
CollisionEngine collisionEngine = Globals.clientSceneWrapper.getCollisionEngine();
collisionEngine.destroyPhysics(modelEntity);
ClientEntityUtils.destroyEntity(modelEntity);
Globals.clientScene.deregisterBehaviorTree(this);
}

View File

@ -17,8 +17,11 @@ import electrosphere.client.scene.ClientWorldData;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.client.terrain.cache.ClientTerrainCache;
import electrosphere.client.terrain.cells.ClientDrawCellManager;
import electrosphere.client.terrain.cells.DrawCell;
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.engine.Globals;
import electrosphere.entity.ClientEntityUtils;
import electrosphere.entity.Entity;
import electrosphere.entity.types.terrain.TerrainChunkData;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.TerrainMessage;
@ -277,11 +280,11 @@ public class ClientTerrainManager {
* @param data The chunk data (triangles, normals, etc)
* @return The model path that is promised to eventually reflect the terrain model when it makes it to gpu
*/
public static String queueTerrainGridGeneration(TerrainChunkData data, VoxelTextureAtlas atlas){
public static String queueTerrainGridGeneration(TerrainChunkData data, VoxelTextureAtlas atlas, DrawCell notifyTarget, Entity toDelete){
String promisedHash = "";
UUID newUUID = UUID.randomUUID();
promisedHash = newUUID.toString();
TerrainChunkGenQueueItem queueItem = new TerrainChunkGenQueueItem(data, promisedHash, atlas);
TerrainChunkGenQueueItem queueItem = new TerrainChunkGenQueueItem(data, promisedHash, atlas, notifyTarget, toDelete);
lock.acquireUninterruptibly();
terrainChunkGenerationQueue.add(queueItem);
lock.release();
@ -297,6 +300,12 @@ public class ClientTerrainManager {
for(TerrainChunkGenQueueItem queueItem : terrainChunkGenerationQueue){
Model terrainModel = TerrainChunkModelGeneration.generateTerrainModel(queueItem.getData(), queueItem.getAtlas());
Globals.assetManager.registerModelToSpecificString(terrainModel, queueItem.getPromisedHash());
if(queueItem.notifyTarget != null){
queueItem.notifyTarget.alertToGeneration();
}
if(queueItem.toDelete != null){
ClientEntityUtils.destroyEntity(queueItem.toDelete);
}
}
terrainChunkGenerationQueue.clear();
lock.release();

View File

@ -1,6 +1,8 @@
package electrosphere.client.terrain.manager;
import electrosphere.client.terrain.cells.DrawCell;
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.entity.Entity;
import electrosphere.entity.types.terrain.TerrainChunkData;
/**
@ -15,16 +17,35 @@ public class TerrainChunkGenQueueItem {
//the texture atlas
VoxelTextureAtlas atlas;
/**
* The draw cell to notify once this model is fully available to render
*/
DrawCell notifyTarget;
/**
* The optional entity to delete on generation of this target
*/
Entity toDelete;
/**
* Creates a queue item
* @param data
* @param promisedHash
* @param atlas
* @param notifyTarget The draw cell to notify once this model is fully available to render
*/
public TerrainChunkGenQueueItem(TerrainChunkData data, String promisedHash, VoxelTextureAtlas atlas){
public TerrainChunkGenQueueItem(
TerrainChunkData data,
String promisedHash,
VoxelTextureAtlas atlas,
DrawCell notifyTarget,
Entity toDelete
){
this.data = data;
this.promisedHash = promisedHash;
this.atlas = atlas;
this.notifyTarget = notifyTarget;
this.toDelete = toDelete;
}
/**

View File

@ -250,6 +250,9 @@ public class ClientLoading {
DrawableUtils.disableCulling(skybox);
EntityUtils.getRotation(skybox).rotateX((float)(-Math.PI/2.0f));
EntityUtils.getScale(skybox).mul(600000.0f);
Globals.clientScene.registerBehaviorTree(() -> {
EntityUtils.getPosition(skybox).set(EntityUtils.getPosition(Globals.playerEntity));
});
Globals.assetManager.queueOverrideMeshShader("Models/environment/skyboxSphere.fbx", "Sphere", "Shaders/entities/skysphere/skysphere.vs", "Shaders/entities/skysphere/skysphere.fs");
//cloud ring pseudo skybox

View File

@ -47,6 +47,7 @@ public class EntityDataStrings {
Terrain Entity
*/
public static final String TERRAIN_IS_TERRAIN = "terrainEntity";
public static final String CORRESPONDING_DRAW_CELL = "correspondingDrawCell";
/*
* Fluid Entity

View File

@ -155,6 +155,16 @@ public class Scene {
behaviorTreeList.add(tree);
}
/**
* Registers a new behavior tree that executes a task
* @param task The task
*/
public void registerBehaviorTree(Runnable task){
this.registerBehaviorTree(new BehaviorTree(){public void simulate(float deltaTime) {
task.run();
}});
}
/**
* Deregisters a behavior tree from the scene
* @param tree The behavior tree to deregister

View File

@ -7,10 +7,12 @@ import org.joml.Quaterniond;
import org.joml.Vector3d;
import electrosphere.client.terrain.cells.ClientDrawCellManager;
import electrosphere.client.terrain.cells.DrawCell;
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.client.terrain.manager.ClientTerrainManager;
import electrosphere.collision.PhysicsEntityUtils;
import electrosphere.engine.Globals;
import electrosphere.entity.ClientEntityUtils;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityDataStrings;
@ -36,11 +38,20 @@ public class TerrainChunk {
/**
* Creates a client terrain chunk based on weights and values provided
* @param chunkData the chunk data to generate with
* @param toDelete The entity to delete on full generation of this entity
* @param notifyTarget The target draw cell to notify once this has successfully generated its model
* @param levelOfDetail Increasing value that increments level of detail. 0 would be full resolution, 1 would be half resolution and so on. Only generates physics if levelOfDetail is 0
* @param hasPolygons true if the chunk has polygons to generate a model with, false otherwise
* @return The terrain chunk entity
*/
public static Entity clientCreateTerrainChunkEntity(TransvoxelChunkData chunkData, int levelOfDetail, VoxelTextureAtlas atlas, boolean hasPolygons){
public static Entity clientCreateTerrainChunkEntity(
TransvoxelChunkData chunkData,
DrawCell notifyTarget,
Entity toDelete,
int levelOfDetail,
VoxelTextureAtlas atlas,
boolean hasPolygons
){
Globals.profiler.beginAggregateCpuSample("TerrainChunk.clientCreateTerrainChunkEntity");
Entity rVal = EntityCreationUtils.createClientSpatialEntity();
@ -52,7 +63,7 @@ public class TerrainChunk {
data = TransvoxelModelGeneration.generateTerrainChunkData(chunkData);
data.constructBuffers();
if(Globals.clientScene.containsEntity(rVal)){
String modelPath = ClientTerrainManager.queueTerrainGridGeneration(data, atlas);
String modelPath = ClientTerrainManager.queueTerrainGridGeneration(data, atlas, notifyTarget, toDelete);
EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath);
if(levelOfDetail == ClientDrawCellManager.FULL_RES_LOD){
PhysicsEntityUtils.clientAttachTerrainChunkRigidBody(rVal, data);
@ -63,6 +74,12 @@ public class TerrainChunk {
}
rVal.putData(EntityDataStrings.HAS_UNIQUE_MODEL, true);
} else {
if(notifyTarget != null){
notifyTarget.alertToGeneration();
}
if(toDelete != null){
ClientEntityUtils.destroyEntity(toDelete);
}
LoggerInterface.loggerEngine.DEBUG("Finished generating terrain polygons; however, entity has already been deleted.");
}
} catch (Error e){
@ -71,6 +88,13 @@ public class TerrainChunk {
LoggerInterface.loggerEngine.ERROR(e);
}
});
} else {
if(notifyTarget != null){
notifyTarget.alertToGeneration();
}
if(toDelete != null){
ClientEntityUtils.destroyEntity(toDelete);
}
}
rVal.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true);