Server-driven block rasterizer with LOD
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2024-11-23 23:27:02 -05:00
parent 749b9c63b4
commit 21e2ec7b06
12 changed files with 1451 additions and 27 deletions

View File

@ -1149,6 +1149,7 @@ Refactoring server side of terrain management
Add server manager for block chunk data & management Add server manager for block chunk data & management
Add endpoint to request strided block data Add endpoint to request strided block data
Add client side handling of block endpoint Add client side handling of block endpoint
Add server-driven block rasterizer with LOD
# TODO # TODO

View File

@ -44,6 +44,31 @@ public class BlockChunkData {
*/ */
public static final int LOD_FULL_RES = 0; public static final int LOD_FULL_RES = 0;
/**
* Lod value for a half res chunk
*/
public static final int LOD_HALF_RES = 1;
/**
* Lod value for a quarter res chunk
*/
public static final int LOD_QUARTER_RES = 2;
/**
* Lod value for a eighth res chunk
*/
public static final int LOD_EIGHTH_RES = 3;
/**
* Lod value for a sixteenth res chunk
*/
public static final int LOD_SIXTEENTH_RES = 4;
/**
* Lod value for the lowest resolution possible
*/
public static final int LOD_LOWEST_RES = LOD_SIXTEENTH_RES;
/** /**
* The type of block at a given position * The type of block at a given position
@ -299,4 +324,12 @@ public class BlockChunkData {
this.homogenousValue = homogenousValue; this.homogenousValue = homogenousValue;
} }
/**
* Sets the homogenous value
* @param homogenousValue The homogenous value
*/
public void setHomogenousValue(int homogenousValue){
this.setHomogenousValue((short)homogenousValue);
}
} }

View File

@ -0,0 +1,315 @@
package electrosphere.client.block.cells;
import org.joml.Quaterniond;
import org.joml.Vector3d;
import org.joml.Vector3i;
import electrosphere.client.block.BlockChunkData;
import electrosphere.engine.Globals;
import electrosphere.entity.ClientEntityUtils;
import electrosphere.entity.Entity;
import electrosphere.entity.btree.BehaviorTree;
import electrosphere.entity.types.terrain.BlockChunkEntity;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
import electrosphere.util.ds.octree.WorldOctTree.WorldOctTreeNode;
import electrosphere.util.math.GeomUtils;
/**
* A single drawcell - contains an entity that has a physics mesh and potentially graphics
*/
public class BlockDrawCell {
/**
* Number of frames to wait before destroying the chunk entity
*/
public static final int FRAMES_TO_WAIT_BEFORE_DESTRUCTION = 25;
/**
* Number of child cells per parent cell
*/
static final int CHILD_CELLS_PER_PARENT = 8;
//the position of the draw cell in world coordinates
Vector3i worldPos;
/**
* The LOD of the draw cell
*/
int lod;
//the main entity for the cell
Entity modelEntity;
/**
* The data for generating the visuals
*/
BlockChunkData chunkData;
/**
* Tracks whether the draw cell has requested its chunk data or not
*/
boolean hasRequested = false;
/**
* Tracks whether the draw cell has generated its entity or not
*/
boolean hasGenerated = false;
/**
* Tracks whether this draw cell is flagged as homogenous from the server or not
*/
boolean homogenous = false;
/**
* Number of failed generation attempts
*/
int failedGenerationAttempts = 0;
/**
* Labels an invalid distance cache
*/
static final int INVALID_DIST_CACHE = -1;
/**
* The cached minimum distance
*/
double cachedMinDistance = -1;
/**
* Target to notify on generation completion
*/
BlockDrawCell notifyTarget = null;
/**
* The number of cells that have alerted this one
*/
int generationAlertCount = 0;
/**
* Private constructor
*/
private BlockDrawCell(){
}
/**
* Constructs a drawcell object
*/
public static BlockDrawCell generateBlockCell(
Vector3i worldPos,
int lod
){
BlockDrawCell rVal = new BlockDrawCell();
rVal.lod = lod;
rVal.worldPos = worldPos;
return rVal;
}
/**
* Generates a drawable entity based on this chunk
*/
public void generateDrawableEntity(BlockTextureAtlas atlas, int lod){
boolean success = true;
if(chunkData == null){
BlockChunkData currentChunk = Globals.clientBlockManager.getChunkDataAtWorldPoint(
worldPos.x,
worldPos.y,
worldPos.z,
lod
);
if(currentChunk == null){
success = false;
} else {
this.homogenous = currentChunk.getHomogenousValue() != BlockChunkData.NOT_HOMOGENOUS;
success = true;
}
if(!success){
this.setFailedGenerationAttempts(this.getFailedGenerationAttempts() + 1);
return;
}
this.chunkData = currentChunk;
}
Entity toDelete = this.modelEntity;
modelEntity = BlockChunkEntity.clientCreateBlockChunkEntity(chunkData, notifyTarget, toDelete, lod, atlas, this.hasPolygons());
ClientEntityUtils.initiallyPositionEntity(modelEntity, this.getRealPos(), new Quaterniond());
this.setHasGenerated(true);
}
/**
* Gets the real-space position of the draw cell
* @return the real-space position
*/
protected Vector3d getRealPos(){
return new Vector3d(
worldPos.x * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET,
worldPos.y * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET,
worldPos.z * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET
);
}
/**
* Gets the world-space position of the draw cell
* @return the world-space position
*/
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(BlockDrawCell 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 >= CHILD_CELLS_PER_PARENT){
this.destroy();
}
}
/**
* Destroys a drawcell including its physics
*/
public void destroy(){
if(modelEntity != null){
Globals.clientScene.registerBehaviorTree(new BehaviorTree(){
int framesSimulated = 0;
public void simulate(float deltaTime) {
if(framesSimulated < FRAMES_TO_WAIT_BEFORE_DESTRUCTION){
framesSimulated++;
} else {
ClientEntityUtils.destroyEntity(modelEntity);
Globals.clientScene.deregisterBehaviorTree(this);
}
}
});
}
}
/**
* Gets the entity for the cell
* @return The entity if it exists, null otherwise
*/
public Entity getEntity(){
return modelEntity;
}
/**
* Transfers chunk data from the source to this draw cell
* @param source The source draw cell
*/
public void transferChunkData(BlockDrawCell source){
this.chunkData = source.chunkData;
this.homogenous = source.homogenous;
this.hasRequested = source.hasRequested;
}
/**
* Gets whether this draw cell has requested its chunk data or not
* @return true if has requested, false otherwise
*/
public boolean hasRequested() {
return hasRequested;
}
/**
* Sets whether this draw cell has requested its chunk data or not
* @param hasRequested true if has requested, false otherwise
*/
public void setHasRequested(boolean hasRequested) {
this.hasRequested = hasRequested;
if(!this.hasRequested){
this.failedGenerationAttempts = 0;
}
}
/**
* Gets whether this draw cell has generated its entity or not
* @return true if has generated, false otherwise
*/
public boolean hasGenerated() {
return hasGenerated;
}
/**
* Sets whether this draw cell has generated its entity or not
* @param hasGenerated true if has generated, false otherwise
*/
public void setHasGenerated(boolean hasGenerated) {
this.hasGenerated = hasGenerated;
}
/**
* Sets whether this draw cell is homogenous or not
* @param hasGenerated true if is homogenous, false otherwise
*/
public void setHomogenous(boolean homogenous) {
this.homogenous = homogenous;
}
/**
* Gets whether this draw cell will generate polygons or not
* @return true if it has polygons, false otherwise
*/
private boolean hasPolygons(){
return !this.homogenous;
}
/**
* Gets the number of failed generation attempts
* @return The number of failed generation attempts
*/
public int getFailedGenerationAttempts(){
return failedGenerationAttempts;
}
/**
* Sets the number of failed generation attempts
* @param attempts The number of failed generation attempts
*/
public void setFailedGenerationAttempts(int attempts){
this.failedGenerationAttempts = this.failedGenerationAttempts + attempts;
}
/**
* Ejects the chunk data
*/
public void ejectChunkData(){
this.chunkData = null;
}
/**
* Gets whether this draw cell is homogenous or not
* @return true if it is homogenous, false otherwise
*/
public boolean isHomogenous(){
return homogenous;
}
/**
* Gets the minimum distance from a node to a point
* @param pos the position to check against
* @param node the node
* @param distCache the lod value under which distance caches are invalidated
* @return the distance
*/
public double getMinDistance(Vector3i worldPos, WorldOctTreeNode<BlockDrawCell> node, int distCache){
if(cachedMinDistance != INVALID_DIST_CACHE && distCache < lod){
return cachedMinDistance;
} else {
this.cachedMinDistance = GeomUtils.getMinSquaredDistanceAABB(worldPos, node.getMinBound(), node.getMaxBound());
return cachedMinDistance;
}
}
}

View File

@ -0,0 +1,80 @@
package electrosphere.client.block.cells;
import java.util.HashMap;
import java.util.Map;
import electrosphere.renderer.texture.Texture;
/**
* An atlas texture and accompanying map of all voxel textures
*/
public class BlockTextureAtlas {
//A map of voxel id -> coordinates in the atlas texture for its texture
Map<Integer,Integer> typeCoordMap = new HashMap<Integer,Integer>();
//the actual texture
Texture specular;
//the normal texture
Texture normal;
//the width in pixels of a single texture in the atlas
public static final int ATLAS_ELEMENT_DIM = 256;
//the width in pixels of the whole atlas texture
public static final int ATLAS_DIM = 8192;
//number of textures per row in the atlas
public static final int ELEMENTS_PER_ROW = ATLAS_DIM / ATLAS_ELEMENT_DIM;
/**
* Puts an entry in the type-coord map to map a voxel type to a position
* @param type the voxel type
* @param coord the coordinate in the map
*/
public void putTypeCoord(int type, int coord){
typeCoordMap.put(type,coord);
}
/**
* Sets the specular
* @param specular the specular
*/
public void setSpecular(Texture specular){
this.specular = specular;
}
/**
* Sets the normal
* @param normal the normal
*/
public void setNormal(Texture normal){
this.normal = normal;
}
/**
* Gets the atlas specular
* @return the atlas specular
*/
public Texture getSpecular(){
return specular;
}
/**
* Gets the atlas normal
* @return the atlas normal
*/
public Texture getNormal(){
return normal;
}
/**
* Gets the index in the atlas of a provided voxel type (the voxel type is provided by its id)
* @param voxelTypeId The id of the voxel type
* @return the index in the atlas of the texture of the provided voxel type
*/
public int getVoxelTypeOffset(int voxelTypeId){
return typeCoordMap.containsKey(voxelTypeId) ? typeCoordMap.get(voxelTypeId) : -1;
}
}

View File

@ -0,0 +1,838 @@
package electrosphere.client.block.cells;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.joml.Vector3d;
import org.joml.Vector3i;
import electrosphere.client.block.BlockChunkData;
import electrosphere.collision.PhysicsEntityUtils;
import electrosphere.engine.Globals;
import electrosphere.entity.EntityUtils;
import electrosphere.logger.LoggerInterface;
import electrosphere.util.ds.octree.WorldOctTree;
import electrosphere.util.ds.octree.WorldOctTree.WorldOctTreeNode;
import electrosphere.util.math.GeomUtils;
/**
* Manages draw cells on the client
*/
public class ClientBlockCellManager {
/**
* Number of times to try updating per frame. Lower this to reduce lag but slow down block mesh generation.
*/
static final int UPDATE_ATTEMPTS_PER_FRAME = 3;
/**
* The number of generation attempts before a cell is marked as having not requested its data
*/
static final int FAILED_GENERATION_ATTEMPT_THRESHOLD = 250;
/**
* The distance to draw at full resolution
*/
public static final double FULL_RES_DIST = 8 * 8;
/**
* The distance for half resolution
*/
public static final double HALF_RES_DIST = 16 * 16;
/**
* The distance for quarter resolution
*/
public static final double QUARTER_RES_DIST = 20 * 20;
/**
* The distance for eighth resolution
*/
public static final double EIGHTH_RES_DIST = 32 * 32;
/**
* The distance for sixteenth resolution
*/
public static final double SIXTEENTH_RES_DIST = 64 * 64;
/**
* The octree holding all the chunks to evaluate
*/
WorldOctTree<BlockDrawCell> chunkTree;
/**
* Tracks what nodes have been evaluated this frame -- used to deduplicate evaluation calls
*/
Map<WorldOctTreeNode<BlockDrawCell>,Boolean> evaluationMap = new HashMap<WorldOctTreeNode<BlockDrawCell>,Boolean>();
/**
* The last recorded player world position
*/
Vector3i lastPlayerPos = new Vector3i();
/**
* Tracks whether the cell manager updated last frame or not
*/
boolean updatedLastFrame = true;
/**
* Controls whether the client draw cell manager should update or not
*/
boolean shouldUpdate = true;
/**
* The voxel texture atlas
*/
BlockTextureAtlas textureAtlas;
/**
* The dimensions of the world
*/
int worldDim = 0;
/**
* Tracks the number of currently valid cells (ie didn't require an update this frame)
*/
int validCellCount = 0;
/**
* The number of maximum resolution chunks
*/
int maxResCount = 0;
/**
* The number of half resolution chunks
*/
int halfResCount = 0;
/**
* The number of generated chunks
*/
int generated = 0;
/**
* Tracks whether the cell manager has initialized or not
*/
boolean initialized = false;
/**
* Constructor
* @param voxelTextureAtlas The voxel texture atlas
* @param worldDim The size of the world in chunks
*/
public ClientBlockCellManager(BlockTextureAtlas voxelTextureAtlas, int worldDim){
this.chunkTree = new WorldOctTree<BlockDrawCell>(
new Vector3i(0,0,0),
new Vector3i(worldDim, worldDim, worldDim)
);
this.chunkTree.getRoot().setData(BlockDrawCell.generateBlockCell(new Vector3i(0,0,0), chunkTree.getMaxLevel()));
this.worldDim = worldDim;
this.textureAtlas = voxelTextureAtlas;
}
/**
* Updates all cells in the chunk
*/
public void update(){
Globals.profiler.beginCpuSample("ClientDrawCellManager.update");
if(shouldUpdate && Globals.playerEntity != null){
Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
Vector3i playerWorldPos = Globals.clientWorldData.convertRealToWorldSpace(playerPos);
int distCache = this.getDistCache(this.lastPlayerPos, playerWorldPos);
this.lastPlayerPos.set(playerWorldPos);
//the sets to iterate through
updatedLastFrame = true;
validCellCount = 0;
evaluationMap.clear();
//update all full res cells
WorldOctTreeNode<BlockDrawCell> rootNode = this.chunkTree.getRoot();
Globals.profiler.beginCpuSample("ClientDrawCellManager.update - full res cells");
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerWorldPos, evaluationMap, BlockChunkData.LOD_LOWEST_RES, distCache);
Globals.profiler.endCpuSample();
if(!updatedLastFrame && !this.initialized){
this.initialized = true;
}
}
Globals.profiler.endCpuSample();
}
/**
* Recursively update child nodes
* @param node The root node
* @param playerPos The player's position
* @param minLeafLod The minimum LOD required to evaluate a leaf
* @param evaluationMap Map of leaf nodes that have been evaluated this frame
* @return true if there is work remaining to be done, false otherwise
*/
private boolean recursivelyUpdateCells(WorldOctTreeNode<BlockDrawCell> node, Vector3i playerPos, Map<WorldOctTreeNode<BlockDrawCell>,Boolean> evaluationMap, int minLeafLod, int distCache){
boolean updated = false;
if(evaluationMap.containsKey(node)){
return false;
}
if(node.getData().hasGenerated() && node.getData().isHomogenous()){
return false;
}
if(node.isLeaf()){
if(this.shouldSplit(playerPos, node, distCache)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.split");
//perform op
WorldOctTreeNode<BlockDrawCell> container = chunkTree.split(node);
BlockDrawCell containerCell = BlockDrawCell.generateBlockCell(container.getMinBound(), this.chunkTree.getMaxLevel() - container.getLevel());
container.setData(containerCell);
container.getData().transferChunkData(node.getData());
//do creations
container.getChildren().forEach(child -> {
Vector3i cellWorldPos = new Vector3i(
child.getMinBound().x,
child.getMinBound().y,
child.getMinBound().z
);
BlockDrawCell drawCell = BlockDrawCell.generateBlockCell(cellWorldPos,this.chunkTree.getMaxLevel() - child.getLevel());
drawCell.registerNotificationTarget(node.getData());
child.setLeaf(true);
child.setData(drawCell);
evaluationMap.put(child,true);
});
//do deletions
this.twoLayerDestroy(node);
//update neighbors
this.conditionalUpdateAdjacentNodes(container, container.getChildren().get(0).getLevel());
Globals.profiler.endCpuSample();
updated = true;
} else if(this.shouldRequest(playerPos, node, minLeafLod, distCache)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.request");
//calculate what to request
BlockDrawCell cell = node.getData();
//actually send requests
if(this.requestChunks(node)){
cell.setHasRequested(true);
}
evaluationMap.put(node,true);
Globals.profiler.endCpuSample();
updated = true;
} else if(this.shouldGenerate(playerPos, node, minLeafLod, distCache)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.generate");
int lodLevel = this.getLODLevel(node);
if(this.containsDataToGenerate(node)){
node.getData().generateDrawableEntity(textureAtlas, lodLevel);
if(node.getData().getFailedGenerationAttempts() > FAILED_GENERATION_ATTEMPT_THRESHOLD){
node.getData().setHasRequested(false);
}
} else if(node.getData() != null){
node.getData().setFailedGenerationAttempts(node.getData().getFailedGenerationAttempts() + 1);
if(node.getData().getFailedGenerationAttempts() > FAILED_GENERATION_ATTEMPT_THRESHOLD){
node.getData().setHasRequested(false);
}
}
evaluationMap.put(node,true);
Globals.profiler.endCpuSample();
updated = true;
}
} else {
if(this.shouldJoin(playerPos, node, distCache)) {
this.join(node);
updated = true;
} else {
this.validCellCount++;
List<WorldOctTreeNode<BlockDrawCell>> children = node.getChildren();
boolean isHomogenous = true;
boolean fullyGenerated = true;
for(int i = 0; i < 8; i++){
WorldOctTreeNode<BlockDrawCell> child = children.get(i);
boolean childUpdate = this.recursivelyUpdateCells(child, playerPos, evaluationMap, minLeafLod, distCache);
if(childUpdate == true){
updated = true;
}
if(!child.getData().hasGenerated()){
fullyGenerated = false;
}
if(!child.getData().isHomogenous()){
isHomogenous = false;
}
}
WorldOctTreeNode<BlockDrawCell> newNode = null;
if(isHomogenous){
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);
}
}
}
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(Vector3i worldPos, WorldOctTreeNode<BlockDrawCell> node, int distCache){
if(node.getData() == null){
return GeomUtils.getMinSquaredDistanceAABB(worldPos, node.getMinBound(), node.getMaxBound());
} else {
return node.getData().getMinDistance(worldPos, node, distCache);
}
}
/**
* Gets the distance cache value
* @param lastPlayerPos The last player world position
* @param currentPlayerPos The current player world position
* @return The distance cache value
*/
private int getDistCache(Vector3i lastPlayerPos, Vector3i currentPlayerPos){
if(
lastPlayerPos.x / 16 != currentPlayerPos.x / 16 || lastPlayerPos.z / 16 != currentPlayerPos.z / 16 || lastPlayerPos.z / 16 != currentPlayerPos.z / 16
){
return this.chunkTree.getMaxLevel();
}
if(
lastPlayerPos.x / 16 != currentPlayerPos.x / 16 || lastPlayerPos.z / 16 != currentPlayerPos.z / 16 || lastPlayerPos.z / 16 != currentPlayerPos.z / 16
){
return BlockChunkData.LOD_SIXTEENTH_RES + 2;
}
if(
lastPlayerPos.x / 8 != currentPlayerPos.x / 8 || lastPlayerPos.z / 8 != currentPlayerPos.z / 8 || lastPlayerPos.z / 8 != currentPlayerPos.z / 8
){
return BlockChunkData.LOD_SIXTEENTH_RES + 1;
}
if(
lastPlayerPos.x / 4 != currentPlayerPos.x / 4 || lastPlayerPos.z / 4 != currentPlayerPos.z / 4 || lastPlayerPos.z / 4 != currentPlayerPos.z / 4
){
return BlockChunkData.LOD_SIXTEENTH_RES;
}
if(
lastPlayerPos.x / 2 != currentPlayerPos.x / 2 || lastPlayerPos.z / 2 != currentPlayerPos.z / 2 || lastPlayerPos.z / 2 != currentPlayerPos.z / 2
){
return BlockChunkData.LOD_EIGHTH_RES;
}
if(
lastPlayerPos.x != currentPlayerPos.x || lastPlayerPos.z != currentPlayerPos.z || lastPlayerPos.z != currentPlayerPos.z
){
return BlockChunkData.LOD_QUARTER_RES;
}
return -1;
}
/**
* 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(Vector3i pos, WorldOctTreeNode<BlockDrawCell> node, int distCache){
//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.canSplit() &&
(node.getLevel() != this.chunkTree.getMaxLevel()) &&
!node.getData().isHomogenous() &&
(node.getParent() != null || node == this.chunkTree.getRoot()) &&
(
(
node.getLevel() < this.chunkTree.getMaxLevel() - BlockChunkData.LOD_SIXTEENTH_RES &&
this.getMinDistance(pos, node, distCache) <= SIXTEENTH_RES_DIST
)
||
(
node.getLevel() < this.chunkTree.getMaxLevel() - BlockChunkData.LOD_EIGHTH_RES &&
this.getMinDistance(pos, node, distCache) <= EIGHTH_RES_DIST
)
||
(
node.getLevel() < this.chunkTree.getMaxLevel() - BlockChunkData.LOD_QUARTER_RES &&
this.getMinDistance(pos, node, distCache) <= QUARTER_RES_DIST
)
||
(
node.getLevel() < this.chunkTree.getMaxLevel() - BlockChunkData.LOD_HALF_RES &&
this.getMinDistance(pos, node, distCache) <= HALF_RES_DIST
)
||
(
node.getLevel() < this.chunkTree.getMaxLevel() &&
this.getMinDistance(pos, node, distCache) <= FULL_RES_DIST
)
)
;
}
/**
* Gets the LOD level of the draw cell
* @param node The node to consider
* @return -1 if outside of render range, -1 if the node is not a valid draw cell leaf, otherwise returns the LOD level
*/
private int getLODLevel(WorldOctTreeNode<BlockDrawCell> node){
return this.chunkTree.getMaxLevel() - node.getLevel();
}
/**
* Conditionally updates all adjacent nodes if their level would require transition cells in the voxel rasterization
* @param node The node to search from adjacencies from
* @param level The level to check against
*/
private void conditionalUpdateAdjacentNodes(WorldOctTreeNode<BlockDrawCell> node, int level){
//don't bother to check if it's a lowest-res chunk
if(this.chunkTree.getMaxLevel() - level > BlockChunkData.LOD_FULL_RES){
return;
}
if(node.getMinBound().x - 1 >= 0){
WorldOctTreeNode<BlockDrawCell> xNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(-1,0,0), false);
if(xNegNode != null && xNegNode.getLevel() < level){
xNegNode.getData().setHasGenerated(false);
}
}
if(node.getMinBound().y - 1 >= 0){
WorldOctTreeNode<BlockDrawCell> yNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(0,-1,0), false);
if(yNegNode != null && yNegNode.getLevel() < level){
yNegNode.getData().setHasGenerated(false);
}
}
if(node.getMinBound().z - 1 >= 0){
WorldOctTreeNode<BlockDrawCell> zNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(0,0,-1), false);
if(zNegNode != null && zNegNode.getLevel() < level){
zNegNode.getData().setHasGenerated(false);
}
}
if(node.getMaxBound().x + 1 < this.worldDim){
WorldOctTreeNode<BlockDrawCell> xPosNode = this.chunkTree.search(new Vector3i(node.getMaxBound()).add(1,-1,-1), false);
if(xPosNode != null && xPosNode.getLevel() < level){
xPosNode.getData().setHasGenerated(false);
}
}
if(node.getMaxBound().y + 1 < this.worldDim){
WorldOctTreeNode<BlockDrawCell> yPosNode = this.chunkTree.search(new Vector3i(node.getMaxBound()).add(-1,1,-1), false);
if(yPosNode != null && yPosNode.getLevel() < level){
yPosNode.getData().setHasGenerated(false);
}
}
if(node.getMaxBound().z + 1 < this.worldDim){
WorldOctTreeNode<BlockDrawCell> zPosNode = this.chunkTree.search(new Vector3i(node.getMaxBound()).add(-1,-1,1), false);
if(zPosNode != null && zPosNode.getLevel() < level){
zPosNode.getData().setHasGenerated(false);
}
}
}
/**
* 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(Vector3i pos, WorldOctTreeNode<BlockDrawCell> node, int distCache){
//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.getLevel() != this.chunkTree.getMaxLevel()) &&
(
(
node.getLevel() == this.chunkTree.getMaxLevel() - BlockChunkData.LOD_HALF_RES &&
this.getMinDistance(pos, node, distCache) > FULL_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - BlockChunkData.LOD_QUARTER_RES &&
this.getMinDistance(pos, node, distCache) > HALF_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - BlockChunkData.LOD_EIGHTH_RES &&
this.getMinDistance(pos, node, distCache) > QUARTER_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - BlockChunkData.LOD_SIXTEENTH_RES &&
this.getMinDistance(pos, node, distCache) > EIGHTH_RES_DIST
)
||
(
this.getMinDistance(pos, node, distCache) > SIXTEENTH_RES_DIST
)
)
;
}
/**
* Joins a parent node
* @param node The parent node
*/
private WorldOctTreeNode<BlockDrawCell> join(WorldOctTreeNode<BlockDrawCell> node){
Globals.profiler.beginCpuSample("ClientDrawCellManager.join");
//queue destructions prior to join -- the join operator clears all children on node
this.recursivelyDestroy(node);
//perform op
BlockDrawCell newLeafCell = BlockDrawCell.generateBlockCell(node.getMinBound(),node.getData().lod);
WorldOctTreeNode<BlockDrawCell> newLeaf = chunkTree.join(node, newLeafCell);
newLeaf.getData().transferChunkData(node.getData());
//update neighbors
this.conditionalUpdateAdjacentNodes(newLeaf, newLeaf.getLevel());
evaluationMap.put(newLeaf,true);
Globals.profiler.endCpuSample();
return newLeaf;
}
/**
* Checks if this cell should request chunk data
* @param pos the player's position
* @param node the node
* @param minLeafLod The minimum LOD required to evaluate a leaf
* @return true if should request chunk data, false otherwise
*/
public boolean shouldRequest(Vector3i pos, WorldOctTreeNode<BlockDrawCell> node, int minLeafLod, int distCache){
return
node.getData() != null &&
!node.getData().hasRequested() &&
(this.chunkTree.getMaxLevel() - node.getLevel()) <= minLeafLod &&
(
(
node.getLevel() == this.chunkTree.getMaxLevel()
// &&
// this.getMinDistance(pos, node) <= FULL_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - BlockChunkData.LOD_HALF_RES
&&
this.getMinDistance(pos, node, distCache) <= QUARTER_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - BlockChunkData.LOD_QUARTER_RES
&&
this.getMinDistance(pos, node, distCache) <= EIGHTH_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - BlockChunkData.LOD_EIGHTH_RES
&&
this.getMinDistance(pos, node, distCache) <= SIXTEENTH_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - BlockChunkData.LOD_SIXTEENTH_RES
&&
this.getMinDistance(pos, node, distCache) <= SIXTEENTH_RES_DIST
)
)
;
}
/**
* Checks if this cell should generate
* @param pos the player's position
* @param node the node
* @param minLeafLod The minimum LOD required to evaluate a leaf
* @return true if should generate, false otherwise
*/
public boolean shouldGenerate(Vector3i pos, WorldOctTreeNode<BlockDrawCell> node, int minLeafLod, int distCache){
return
!node.getData().hasGenerated() &&
(this.chunkTree.getMaxLevel() - node.getLevel()) <= minLeafLod &&
(
(
node.getLevel() == this.chunkTree.getMaxLevel()
// &&
// this.getMinDistance(pos, node) <= FULL_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - BlockChunkData.LOD_HALF_RES
&&
this.getMinDistance(pos, node, distCache) <= QUARTER_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - BlockChunkData.LOD_QUARTER_RES
&&
this.getMinDistance(pos, node, distCache) <= EIGHTH_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - BlockChunkData.LOD_EIGHTH_RES
&&
this.getMinDistance(pos, node, distCache) <= SIXTEENTH_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - BlockChunkData.LOD_SIXTEENTH_RES
&&
this.getMinDistance(pos, node, distCache) <= SIXTEENTH_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(WorldOctTreeNode<BlockDrawCell> node){
return
node.getData() != null &&
node.getData().getEntity() != null
;
}
/**
* 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(WorldOctTreeNode<BlockDrawCell> node){
if(node.getChildren().size() > 0){
for(WorldOctTreeNode<BlockDrawCell> child : node.getChildren()){
child.getData().destroy();
}
// node.getChildren().forEach(child -> recursivelyDestroy(child));
}
if(node.getData() != null){
node.getData().destroy();
}
}
/**
* Destroys two layers of nodes
* @param node The top node
*/
private void twoLayerDestroy(WorldOctTreeNode<BlockDrawCell> node){
if(!node.getData().hasGenerated()){
for(WorldOctTreeNode<BlockDrawCell> child : node.getChildren()){
child.getData().destroy();
}
} else {
node.getData().destroy();
}
}
/**
* Checks if the cell manager made an update last frame or not
* @return true if an update occurred, false otherwise
*/
public boolean updatedLastFrame(){
return this.updatedLastFrame;
}
/**
* Checks if the position is within the full LOD range
* @param worldPos The world position
* @return true if within full LOD range, false otherwise
*/
public boolean isFullLOD(Vector3i worldPos){
Vector3d playerRealPos = EntityUtils.getPosition(Globals.playerEntity);
Vector3d chunkMin = Globals.clientWorldData.convertWorldToRealSpace(worldPos);
Vector3d chunkMax = Globals.clientWorldData.convertWorldToRealSpace(new Vector3i(worldPos).add(1,1,1));
return GeomUtils.getMinDistanceAABB(playerRealPos, chunkMin, chunkMax) <= FULL_RES_DIST;
}
/**
* Evicts all cells
*/
public void evictAll(){
this.recursivelyDestroy(this.chunkTree.getRoot());
this.chunkTree.clear();
}
/**
* Marks a draw cell as updateable
* @param worldX The world x position
* @param worldY The world y position
* @param worldZ The world z position
*/
public void markUpdateable(int worldX, int worldY, int worldZ){
BlockDrawCell drawCell = this.getDrawCell(worldX, worldY, worldZ);
drawCell.ejectChunkData();
drawCell.setHasGenerated(false);
drawCell.setHasRequested(false);
}
/**
* Requests all chunks for a given draw cell
* @param cell The cell
* @return true if all cells were successfully requested, false otherwise
*/
private boolean requestChunks(WorldOctTree.WorldOctTreeNode<BlockDrawCell> node){
int lod = this.chunkTree.getMaxLevel() - node.getLevel();
Vector3i worldPos = node.getMinBound();
if(
worldPos.x >= 0 &&
worldPos.x < Globals.clientWorldData.getWorldDiscreteSize() &&
worldPos.y >= 0 &&
worldPos.y < Globals.clientWorldData.getWorldDiscreteSize() &&
worldPos.z >= 0 &&
worldPos.z < Globals.clientWorldData.getWorldDiscreteSize() &&
!Globals.clientBlockManager.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, lod)
){
//client should request chunk data from server for each chunk necessary to create the model
LoggerInterface.loggerNetworking.DEBUG("(Client) Send Request for block data at " + worldPos);
if(!Globals.clientBlockManager.requestChunk(worldPos.x, worldPos.y, worldPos.z, lod)){
return false;
}
}
return true;
}
/**
* Checks if all chunk data required to generate this draw cell is present
* @param node The node
* @return true if all data is available, false otherwise
*/
private boolean containsDataToGenerate(WorldOctTree.WorldOctTreeNode<BlockDrawCell> node){
BlockDrawCell cell = node.getData();
int lod = this.chunkTree.getMaxLevel() - node.getLevel();
Vector3i worldPos = cell.getWorldPos();
if(!Globals.clientBlockManager.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, lod)){
return false;
}
return true;
}
/**
* Sets whether the draw cell manager should update or not
* @param shouldUpdate true if should update, false otherwise
*/
public void setShouldUpdate(boolean shouldUpdate){
this.shouldUpdate = shouldUpdate;
}
/**
* Gets whether the client draw cell manager should update or not
* @return true if should update, false otherwise
*/
public boolean getShouldUpdate(){
return this.shouldUpdate;
}
/**
* Gets the number of currently valid cells
* @return The number of currently valid cells
*/
public int getValidCellCount(){
return validCellCount;
}
/**
* Calculates the status of the draw cell manager
*/
public void updateStatus(){
maxResCount = 0;
halfResCount = 0;
generated = 0;
this.recursivelyCalculateStatus(this.chunkTree.getRoot());
}
/**
* Recursively calculates the status of the manager
* @param node The root node
*/
private void recursivelyCalculateStatus(WorldOctTreeNode<BlockDrawCell> node){
if(node.getLevel() == this.chunkTree.getMaxLevel() - 1){
halfResCount++;
}
if(node.getLevel() == this.chunkTree.getMaxLevel()){
maxResCount++;
}
if(node.getData() != null && node.getData().hasGenerated()){
generated++;
}
if(node.getChildren() != null && node.getChildren().size() > 0){
List<WorldOctTreeNode<BlockDrawCell>> children = new LinkedList<WorldOctTreeNode<BlockDrawCell>>(node.getChildren());
for(WorldOctTreeNode<BlockDrawCell> child : children){
recursivelyCalculateStatus(child);
}
}
}
/**
* Gets The number of maximum resolution chunks
* @return The number of maximum resolution chunks
*/
public int getMaxResCount() {
return maxResCount;
}
/**
* Gets The number of half resolution chunks
* @return The number of half resolution chunks
*/
public int getHalfResCount() {
return halfResCount;
}
/**
* Gets The number of generated chunks
* @return
*/
public int getGenerated() {
return generated;
}
/**
* Gets whether the client draw cell manager has initialized or not
* @return true if it has initialized, false otherwise
*/
public boolean isInitialized(){
return this.initialized;
}
/**
* Gets the draw cell for a given world coordinate if it has been generated
* @param worldX The world x coordinate
* @param worldY The world y coordinate
* @param worldZ The world z coordinate
* @return The draw cell if it exists, null otherwise
*/
public BlockDrawCell getDrawCell(int worldX, int worldY, int worldZ){
WorldOctTreeNode<BlockDrawCell> node = this.chunkTree.search(new Vector3i(worldX,worldY,worldZ), false);
if(node != null){
return node.getData();
}
return null;
}
/**
* Checks if physics has been generated for a given world coordinate
* @param worldX The world x coordinate
* @param worldY The world y coordinate
* @param worldZ The world z coordinate
* @return true if physics has been generated, false otherwise
*/
public boolean hasGeneratedPhysics(int worldX, int worldY, int worldZ){
BlockDrawCell cell = this.getDrawCell(worldX, worldY, worldZ);
if(cell != null && cell.getEntity() != null){
return PhysicsEntityUtils.containsDBody(cell.getEntity());
}
return false;
}
}

View File

@ -209,7 +209,9 @@ public class ClientSimulation {
* Updates the block cell manager * Updates the block cell manager
*/ */
private void updateBlockCellManager(){ private void updateBlockCellManager(){
if(Globals.clientBlockCellManager != null && Globals.clientWorldData != null){
Globals.clientBlockCellManager.update();
}
} }
/** /**

View File

@ -14,6 +14,8 @@ import electrosphere.audio.collision.HitboxAudioService;
import electrosphere.audio.movement.MovementAudioService; import electrosphere.audio.movement.MovementAudioService;
import electrosphere.auth.AuthenticationManager; import electrosphere.auth.AuthenticationManager;
import electrosphere.client.block.ClientBlockManager; import electrosphere.client.block.ClientBlockManager;
import electrosphere.client.block.cells.BlockTextureAtlas;
import electrosphere.client.block.cells.ClientBlockCellManager;
import electrosphere.client.chemistry.ClientChemistryCollisionCallback; import electrosphere.client.chemistry.ClientChemistryCollisionCallback;
import electrosphere.client.entity.particle.ParticleService; import electrosphere.client.entity.particle.ParticleService;
import electrosphere.client.fluid.cells.FluidCellManager; import electrosphere.client.fluid.cells.FluidCellManager;
@ -362,6 +364,8 @@ public class Globals {
//draw cell manager //draw cell manager
public static ClientDrawCellManager clientDrawCellManager; public static ClientDrawCellManager clientDrawCellManager;
public static VoxelTextureAtlas voxelTextureAtlas = new VoxelTextureAtlas(); public static VoxelTextureAtlas voxelTextureAtlas = new VoxelTextureAtlas();
public static ClientBlockCellManager clientBlockCellManager;
public static BlockTextureAtlas blockTextureAtlas = null;
//fluid cell manager //fluid cell manager
public static FluidCellManager fluidCellManager; public static FluidCellManager fluidCellManager;

View File

@ -5,7 +5,7 @@ import java.util.concurrent.TimeUnit;
import org.joml.Vector3f; import org.joml.Vector3f;
import electrosphere.client.block.BlockChunkData; import electrosphere.client.block.cells.ClientBlockCellManager;
import electrosphere.client.entity.camera.CameraEntityUtils; import electrosphere.client.entity.camera.CameraEntityUtils;
import electrosphere.client.entity.crosshair.Crosshair; import electrosphere.client.entity.crosshair.Crosshair;
import electrosphere.client.fluid.cells.FluidCellManager; import electrosphere.client.fluid.cells.FluidCellManager;
@ -19,7 +19,6 @@ import electrosphere.client.ui.menu.mainmenu.MenuCharacterCreation;
import electrosphere.controls.ControlHandler; import electrosphere.controls.ControlHandler;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.engine.assetmanager.AssetDataStrings; import electrosphere.engine.assetmanager.AssetDataStrings;
import electrosphere.engine.assetmanager.queue.QueuedModel;
import electrosphere.engine.signal.Signal.SignalType; import electrosphere.engine.signal.Signal.SignalType;
import electrosphere.engine.threads.LabeledThread.ThreadLabel; import electrosphere.engine.threads.LabeledThread.ThreadLabel;
import electrosphere.entity.DrawableUtils; import electrosphere.entity.DrawableUtils;
@ -31,8 +30,6 @@ import electrosphere.net.NetUtils;
import electrosphere.net.client.ClientNetworking; import electrosphere.net.client.ClientNetworking;
import electrosphere.renderer.actor.Actor; import electrosphere.renderer.actor.Actor;
import electrosphere.renderer.actor.ActorTextureMask; import electrosphere.renderer.actor.ActorTextureMask;
import electrosphere.renderer.meshgen.BlockMeshgen;
import electrosphere.renderer.meshgen.BlockMeshgen.BlockMeshData;
public class ClientLoading { public class ClientLoading {
@ -95,12 +92,11 @@ public class ClientLoading {
Globals.controlHandler.hintUpdateControlState(ControlHandler.ControlsState.NO_INPUT); Globals.controlHandler.hintUpdateControlState(ControlHandler.ControlsState.NO_INPUT);
//initialize the "real" objects simulation //initialize the "real" objects simulation
initClientSimulation(); initClientSimulation();
//initialize the cell manager (client) //initialize the gridded managers (client)
initDrawCellManager(true); initDrawCellManager(true);
//init foliage manager
initFoliageManager(); initFoliageManager();
//init the fluid cell manager
initFluidCellManager(true); initFluidCellManager(true);
initBlockCellManager(true);
//initialize the basic graphical entities of the world (skybox, camera) //initialize the basic graphical entities of the world (skybox, camera)
initWorldBaseGraphicalEntities(); initWorldBaseGraphicalEntities();
//init arena specific stuff (ie different skybox colors) //init arena specific stuff (ie different skybox colors)
@ -141,11 +137,11 @@ public class ClientLoading {
Globals.cameraHandler.setUpdate(false); Globals.cameraHandler.setUpdate(false);
//initialize the "real" objects simulation //initialize the "real" objects simulation
initClientSimulation(); initClientSimulation();
//init foliage manager
initFoliageManager();
//initialize the cell managers (client) //initialize the cell managers (client)
initDrawCellManager(false); initDrawCellManager(false);
initFluidCellManager(false); initFluidCellManager(false);
initBlockCellManager(false);
initFoliageManager();
//sets micro and macro sims to ready if they exist //sets micro and macro sims to ready if they exist
setSimulationsToReady(); setSimulationsToReady();
@ -276,23 +272,6 @@ public class ClientLoading {
cursorActor.addTextureMask(new ActorTextureMask("sphere", Arrays.asList(new String[]{"Textures/transparent_red.png"}))); cursorActor.addTextureMask(new ActorTextureMask("sphere", Arrays.asList(new String[]{"Textures/transparent_red.png"})));
DrawableUtils.makeEntityTransparent(Globals.playerCursor); DrawableUtils.makeEntityTransparent(Globals.playerCursor);
EntityUtils.getScale(Globals.playerCursor).set(0.2f); EntityUtils.getScale(Globals.playerCursor).set(0.2f);
//block test
Entity blockEntity = EntityCreationUtils.createClientSpatialEntity();
BlockChunkData blockChunkData = BlockChunkData.allocate();
blockChunkData.setType(0, 0, 0, 1);
blockChunkData.setType(1, 0, 0, 1);
blockChunkData.setType(0, 1, 0, 1);
blockChunkData.setType(1, 1, 0, 1);
blockChunkData.setType(0, 0, 1, 1);
blockChunkData.setType(1, 0, 1, 1);
blockChunkData.setType(0, 1, 1, 1);
blockChunkData.setType(1, 1, 1, 1);
BlockMeshData meshData = BlockMeshgen.rasterize(blockChunkData);
String modelPath = Globals.assetManager.queuedAsset(new QueuedModel(() -> {
return BlockMeshgen.generateBlockModel(meshData);
}));
EntityCreationUtils.makeEntityDrawablePreexistingModel(blockEntity, modelPath);
} }
static final int MAX_DRAW_CELL_WAIT = 1000; static final int MAX_DRAW_CELL_WAIT = 1000;
@ -386,6 +365,52 @@ public class ClientLoading {
} }
} }
/**
* Inits the block cell manager
* @param blockForInit Blocks the thread until the block cell manager is ready
*/
static void initBlockCellManager(boolean blockForInit){
int iterations = 0;
WindowUtils.updateLoadingWindow("WAITING ON WORLD DATA");
while(blockForInit && (Globals.clientWorldData == null || InitialAssetLoading.atlasQueuedTexture == null || !InitialAssetLoading.atlasQueuedTexture.hasLoaded()) && Globals.threadManager.shouldKeepRunning()){
try {
TimeUnit.MILLISECONDS.sleep(10);
iterations++;
} catch (InterruptedException ex) {
LoggerInterface.loggerEngine.ERROR(ex);
}
if(iterations > MAX_DRAW_CELL_WAIT){
String message = "Draw cell took too long to init!\n" +
Globals.clientWorldData + "\n" +
InitialAssetLoading.atlasQueuedTexture.hasLoaded();
throw new IllegalStateException(message);
}
}
Globals.clientBlockCellManager = new ClientBlockCellManager(Globals.blockTextureAtlas, Globals.clientWorldData.getWorldDiscreteSize());
//Alerts the client simulation that it should start loading blocks
Globals.clientSimulation.setLoadingTerrain(true);
//wait for all the block data to arrive
int i = 0;
while(
blockForInit &&
!Globals.clientBlockCellManager.isInitialized() &&
Globals.threadManager.shouldKeepRunning()
){
i++;
if(i % DRAW_CELL_UPDATE_RATE == 0){
WindowUtils.updateLoadingWindow("WAITING ON SERVER TO SEND BLOCKS (" + Globals.clientTerrainManager.getAllChunks().size() + ")");
}
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
if(i < DRAW_CELL_EXPECTED_MINIMUM_FRAMES_TO_INIT){
LoggerInterface.loggerEngine.WARNING("Block cell manager loaded exceptionally fast!");
}
}
/** /**
* Starts up the foliage manager * Starts up the foliage manager
*/ */

View File

@ -10,6 +10,7 @@ import electrosphere.client.terrain.foliage.FoliageModel;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.engine.loadingthreads.LoadingThread; import electrosphere.engine.loadingthreads.LoadingThread;
import electrosphere.engine.threads.LabeledThread.ThreadLabel; import electrosphere.engine.threads.LabeledThread.ThreadLabel;
import electrosphere.entity.types.terrain.BlockChunkEntity;
import electrosphere.entity.types.terrain.TerrainChunk; import electrosphere.entity.types.terrain.TerrainChunk;
import electrosphere.server.datacell.Realm; import electrosphere.server.datacell.Realm;
import electrosphere.util.CodeUtils; import electrosphere.util.CodeUtils;
@ -118,6 +119,9 @@ public class ThreadManager {
if(realm.getServerWorldData() != null && realm.getServerWorldData().getServerTerrainManager() != null){ if(realm.getServerWorldData() != null && realm.getServerWorldData().getServerTerrainManager() != null){
realm.getServerWorldData().getServerTerrainManager().closeThreads(); realm.getServerWorldData().getServerTerrainManager().closeThreads();
} }
if(realm.getServerWorldData() != null && realm.getServerWorldData().getServerBlockManager() != null){
realm.getServerWorldData().getServerBlockManager().closeThreads();
}
} }
} }
@ -126,6 +130,7 @@ public class ThreadManager {
*/ */
TerrainChunk.haltThreads(); TerrainChunk.haltThreads();
FoliageModel.haltThreads(); FoliageModel.haltThreads();
BlockChunkEntity.haltThreads();
// //
//interrupt all threads //interrupt all threads

View File

@ -0,0 +1,109 @@
package electrosphere.entity.types.terrain;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.joml.Quaterniond;
import org.joml.Vector3d;
import electrosphere.client.block.BlockChunkData;
import electrosphere.client.block.cells.BlockDrawCell;
import electrosphere.client.block.cells.BlockTextureAtlas;
import electrosphere.engine.Globals;
import electrosphere.engine.assetmanager.queue.QueuedModel;
import electrosphere.entity.ClientEntityUtils;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.types.collision.CollisionObjUtils;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.meshgen.BlockMeshgen;
import electrosphere.renderer.meshgen.BlockMeshgen.BlockMeshData;
/**
* Generates block chunk entities
*/
public class BlockChunkEntity {
/**
* Used for generating block chunks
*/
static final ExecutorService generationService = Executors.newFixedThreadPool(4);
/**
* Creates a client block 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 block chunk entity
*/
public static Entity clientCreateBlockChunkEntity(
BlockChunkData chunkData,
BlockDrawCell notifyTarget,
Entity toDelete,
int levelOfDetail,
BlockTextureAtlas atlas,
boolean hasPolygons
){
Globals.profiler.beginAggregateCpuSample("BlockChunk.clientCreateBlockChunkEntity");
Entity rVal = EntityCreationUtils.createClientSpatialEntity();
if(hasPolygons && chunkData.getType() != null && chunkData.getMetadata() != null){
generationService.submit(() -> {
BlockMeshData data;
try {
data = BlockMeshgen.rasterize(chunkData);
if(Globals.clientScene.containsEntity(rVal)){
String modelPath = Globals.assetManager.queuedAsset(new QueuedModel(() -> {
return BlockMeshgen.generateBlockModel(data);
}));
EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath);
if(levelOfDetail == BlockChunkData.LOD_FULL_RES){
// PhysicsEntityUtils.clientAttachTerrainChunkRigidBody(rVal, data);
CollisionObjUtils.clientPositionCharacter(rVal, new Vector3d(EntityUtils.getPosition(rVal)), new Quaterniond());
} else {
EntityCreationUtils.bypassShadowPass(rVal);
EntityCreationUtils.bypassVolumetics(rVal);
}
rVal.putData(EntityDataStrings.HAS_UNIQUE_MODEL, true);
} else {
if(notifyTarget != null){
notifyTarget.alertToGeneration();
}
if(toDelete != null){
ClientEntityUtils.destroyEntity(toDelete);
}
LoggerInterface.loggerEngine.DEBUG("Finished generating block polygons; however, entity has already been deleted.");
}
} catch (Error e){
LoggerInterface.loggerEngine.ERROR(e);
} catch(Exception e){
LoggerInterface.loggerEngine.ERROR(e);
}
});
} else {
if(notifyTarget != null){
notifyTarget.alertToGeneration();
}
if(toDelete != null){
ClientEntityUtils.destroyEntity(toDelete);
}
}
rVal.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true);
Globals.profiler.endCpuSample();
return rVal;
}
/**
* Halts all running generation threads
*/
public static void haltThreads(){
generationService.shutdownNow();
}
}

View File

@ -104,6 +104,17 @@ public class ServerBlockChunkGenerationThread implements Runnable {
if(chunk == null){ if(chunk == null){
//TODO: generate from macro-level data //TODO: generate from macro-level data
chunk = BlockChunkData.allocate(); chunk = BlockChunkData.allocate();
chunk.setHomogenousValue(0);
if(worldX == 0 && worldY == 0 && worldZ == 0){
chunk.setHomogenousValue(BlockChunkData.NOT_HOMOGENOUS);
for(int x = 0; x < 16; x++){
for(int y = 0; y < 16; y++){
for(int z = 0; z < 16; z++){
chunk.setType(x, y, z, 1);
}
}
}
}
} }
if(chunk != null){ if(chunk != null){
chunkCache.add(worldX, worldY, worldZ, stride, chunk); chunkCache.add(worldX, worldY, worldZ, stride, chunk);

View File

@ -120,6 +120,7 @@ public class ServerBlockManager {
returnedChunk.setWorldX(worldX); returnedChunk.setWorldX(worldX);
returnedChunk.setWorldY(worldY); returnedChunk.setWorldY(worldY);
returnedChunk.setWorldZ(worldZ); returnedChunk.setWorldZ(worldZ);
returnedChunk.setHomogenousValue(0);
} }
this.chunkCache.add(worldX, worldY, worldZ, BlockChunkData.LOD_FULL_RES, returnedChunk); this.chunkCache.add(worldX, worldY, worldZ, BlockChunkData.LOD_FULL_RES, returnedChunk);
} }