fixes to foliage rendering
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2024-11-08 17:54:48 -05:00
parent 795fbb6a1c
commit a9c74e8d50
27 changed files with 250 additions and 123 deletions

View File

@ -1,3 +1,3 @@
#maven.buildNumber.plugin properties file
#Mon Nov 04 12:41:10 EST 2024
buildNumber=375
#Fri Nov 08 15:44:15 EST 2024
buildNumber=376

View File

@ -954,6 +954,12 @@ Fix LOD bounding sphere calculation
Hook up content generation to test generation realm
Reorganize biome data
(11/08/2024)
Player and entity tracking overhaul in grid data cell manager
Add more profiling points
Height manual adjustment for content placement
Fast track client draw cell manager cell evaluation
# TODO

View File

@ -99,7 +99,7 @@ public class ClientFoliageManager {
} else {
chunkUpdateCache.remove(foundChunk);
}
foundChunk.updateCells(false);
foundChunk.updateCells();
Globals.profiler.endCpuSample();
}
@ -144,7 +144,7 @@ public class ClientFoliageManager {
public void evaluateChunk(Vector3i worldPos){
for(FoliageChunk chunk : chunkUpdateCache){
if(chunk.getWorldPos().equals(worldPos)){
chunk.updateCells(true);
chunk.updateCells();
break;
}
}

View File

@ -27,6 +27,7 @@ import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.state.foliage.AmbientFoliage;
import electrosphere.game.data.foliage.type.FoliageType;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.OpenGLState;
import electrosphere.renderer.RenderPipelineState;
import electrosphere.renderer.actor.instance.TextureInstancedActor;
@ -60,6 +61,11 @@ public class FoliageCell {
*/
static final float SAMPLE_START_HEIGHT = 1.0f;
/**
* The ID of the air voxel
*/
static final int AIR_VOXEL_ID = 0;
/**
* <p>
* Size of a single item of foliage in the texture buffer
@ -148,6 +154,11 @@ public class FoliageCell {
*/
static Sphered boundingSphere = new Sphered(0.5,0.5,0.5,2);
/**
* Tracks whether the cell has generated or not
*/
boolean hasGenerated = false;
/**
* Inits the foliage cell data
*/
@ -204,17 +215,17 @@ public class FoliageCell {
*/
protected void generate(){
boolean shouldGenerate = false;
//get foliage types supported
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPosition,ChunkData.NO_STRIDE);
if(data == null){
if(voxelPosition.y + 1 >= ServerTerrainChunk.CHUNK_DIMENSION){
return;
}
if(voxelPosition.y + 1 >= ServerTerrainChunk.CHUNK_DIMENSION){
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPosition,ChunkData.NO_STRIDE);
if(data == null){
return;
}
if(!Globals.clientDrawCellManager.isFullLOD(worldPosition)){
return;
}
//get foliage types supported
List<String> foliageTypesSupported = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(data.getType(voxelPosition)).getAmbientFoliage();
boolean airAbove = data.getType(voxelPosition.x,voxelPosition.y+1,voxelPosition.z) == 0;
if(foliageTypesSupported != null && foliageTypesSupported.size() > 0 && airAbove && scale < 3){
@ -227,12 +238,15 @@ public class FoliageCell {
//create cell and buffer
ByteBuffer buffer = BufferUtils.createByteBuffer(TARGET_FOLIAGE_PER_CELL * SINGLE_FOLIAGE_DATA_SIZE_BYTES);
if(buffer.capacity() < TARGET_FOLIAGE_PER_CELL * SINGLE_FOLIAGE_DATA_SIZE_BYTES){
LoggerInterface.loggerEngine.WARNING("Failed to allocate data for foliage cell! " + buffer.limit());
}
FloatBuffer floatBufferView = buffer.asFloatBuffer();
int drawCount = 0;
for(int x = 0; x < scale; x++){
for(int y = 0; y < scale; y++){
for(int z = 0; z < scale; z++){
drawCount = drawCount + insertBlades(x, y, z, floatBufferView, data);
drawCount = drawCount + this.insertBlades(x, y, z, floatBufferView, data);
}
}
}
@ -255,6 +269,7 @@ public class FoliageCell {
this.addEntity(grassEntity);
}
}
this.hasGenerated = true;
}
/**
@ -278,7 +293,7 @@ public class FoliageCell {
List<String> foliageTypesSupported = null;
if(chunkData != null && currVoxelPos.y + 1 < ServerTerrainChunk.CHUNK_DIMENSION){
foliageTypesSupported = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(chunkData.getType(currVoxelPos)).getAmbientFoliage();
boolean airAbove = chunkData.getType(currVoxelPos.x,currVoxelPos.y+1,currVoxelPos.z) == 0;
boolean airAbove = chunkData.getType(currVoxelPos.x,currVoxelPos.y+1,currVoxelPos.z) == AIR_VOXEL_ID;
if(foliageTypesSupported != null && airAbove){
shouldGenerate = true;
}
@ -387,12 +402,14 @@ public class FoliageCell {
offsetY = offsetY - realPosition.y;
double rotVar = placementRandomizer.nextDouble() * Math.PI * 2;
double rotVar2 = placementRandomizer.nextDouble();
floatBufferView.put((float)offsetX + vX);
floatBufferView.put((float)offsetY + vY);
floatBufferView.put((float)offsetZ + vZ);
floatBufferView.put((float)rotVar);
floatBufferView.put((float)rotVar2);
rVal++;
if(floatBufferView.limit() >= floatBufferView.position() + 4){
floatBufferView.put((float)offsetX + vX);
floatBufferView.put((float)offsetY + vY);
floatBufferView.put((float)offsetZ + vZ);
floatBufferView.put((float)rotVar);
floatBufferView.put((float)rotVar2);
rVal++;
}
}
}
}
@ -463,6 +480,14 @@ public class FoliageCell {
}
}
/**
* Gets whether the cell has generated or not
* @return true if has generated, false otherwise
*/
public boolean hasGenerated(){
return this.hasGenerated;
}
/**
* SCAFFOLDING FOR BUILDING SCALE>1 CELLS AND ALSO FOR TOP LEVEL CELL CHECKING

View File

@ -39,6 +39,12 @@ public class FoliageChunk {
*/
boolean containsFoliageVoxel = false;
/**
* Tracks whether this foliage chunk has properly initialized or not.
* It is only considered initialized once it has been evaluated with the data present in cache.
*/
boolean initialized = false;
/**
* The octree holding all the chunks to evaluate
*/
@ -80,21 +86,19 @@ public class FoliageChunk {
this.currentChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos,ChunkData.NO_STRIDE);
// //evaluate top cells if chunk above this one exists
this.aboveChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(worldPos).add(0,1,0),ChunkData.NO_STRIDE);
this.updateCells(true);
this.updateCells();
Globals.profiler.endCpuSample();
}
/**
* Updates all cells in the chunk
* @param force true if should ignore cache, false otherwise
*/
public void updateCells(boolean force){
public void updateCells(){
Globals.profiler.beginCpuSample("FoliageChunk.updateCells");
//re-evaluate whether contains foliage voxel or not
if(force){
this.containsFoliageVoxel = checkContainsFoliageVoxel();
}
if(force || containsFoliageVoxel){
boolean hasData = Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, ChunkData.NO_STRIDE);
boolean hasPhysics = Globals.clientDrawCellManager.hasGeneratedPhysics(worldPos.x, worldPos.y, worldPos.z);
if(containsFoliageVoxel && hasData && hasPhysics){
Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
//the sets to iterate through
boolean updated = true;
@ -105,6 +109,13 @@ public class FoliageChunk {
attempts++;
}
}
if(!hasData){
Globals.clientTerrainManager.requestChunk(worldPos.x, worldPos.y, worldPos.z, ChunkData.NO_STRIDE);
}
if(hasData && !this.initialized) {
this.evaluateContainsFoliage();
this.initialized = true;
}
Globals.profiler.endCpuSample();
}
@ -130,6 +141,13 @@ public class FoliageChunk {
return false;
}
/**
* Instructs the chunk to evaluate whether it has foliage or not
*/
public void evaluateContainsFoliage(){
this.containsFoliageVoxel = this.checkContainsFoliageVoxel();
}
/**
* Recursively update child nodes
* @param node The root node
@ -137,8 +155,9 @@ public class FoliageChunk {
*/
private boolean recursivelyUpdateCells(ChunkTreeNode<FoliageCell> node, Vector3d playerPos){
boolean updated = false;
Globals.profiler.beginRecursiveCpuSample("FoliageChunk.recursivelyUpdateCells");
if(this.shouldSplit(playerPos, node)){
Globals.profiler.beginCpuSample("FoliageChunk.split");
Globals.profiler.beginAggregateCpuSample("FoliageChunk.split");
//perform op
ChunkTreeNode<FoliageCell> container = chunkTree.split(node);
@ -157,7 +176,7 @@ public class FoliageChunk {
Globals.profiler.endCpuSample();
updated = true;
} else if(this.shouldJoin(playerPos, node)) {
// Globals.profiler.beginCpuSample("FoliageChunk.join");
Globals.profiler.beginAggregateCpuSample("FoliageChunk.join");
//perform op
ChunkTreeNode<FoliageCell> newLeaf = chunkTree.join(node);
@ -166,12 +185,12 @@ public class FoliageChunk {
//do creations
newLeaf.convertToLeaf(new FoliageCell(worldPos, newLeaf.getMinBound(), realPos, 5 - newLeaf.getLevel()));
// Globals.profiler.endCpuSample();
Globals.profiler.endCpuSample();
updated = true;
} else if(shouldGenerate(playerPos, node)){
// Globals.profiler.beginCpuSample("FoliageChunk.generate");
Globals.profiler.beginAggregateCpuSample("FoliageChunk.generate");
node.getData().generate();
// Globals.profiler.endCpuSample();
Globals.profiler.endCpuSample();
updated = true;
} else if(!node.isLeaf()){
List<ChunkTreeNode<FoliageCell>> children = new LinkedList<ChunkTreeNode<FoliageCell>>(node.getChildren());
@ -180,6 +199,7 @@ public class FoliageChunk {
updated = childUpdate || updated;
}
}
Globals.profiler.endCpuSample();
return updated;
}
@ -264,7 +284,7 @@ public class FoliageChunk {
return
node.isLeaf() &&
node.getData() != null &&
node.getData().containedEntities.size() < 1 &&
!node.getData().hasGenerated() &&
(
(
node.getLevel() == ChunkTree.MAX_LEVEL

View File

@ -1,12 +1,15 @@
package electrosphere.client.terrain.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.terrain.cells.DrawCell.DrawCellFace;
import electrosphere.collision.PhysicsEntityUtils;
import electrosphere.engine.Globals;
import electrosphere.entity.EntityUtils;
import electrosphere.logger.LoggerInterface;
@ -90,6 +93,11 @@ public class ClientDrawCellManager {
*/
WorldOctTree<DrawCell> chunkTree;
/**
* Tracks what nodes have been evaluated this frame -- used to deduplicate evaluation calls
*/
Map<FloatingChunkTreeNode<DrawCell>,Boolean> evaluationMap = new HashMap<FloatingChunkTreeNode<DrawCell>,Boolean>();
/**
* Tracks whether the cell manager updated last frame or not
*/
@ -150,29 +158,40 @@ public class ClientDrawCellManager {
* Updates all cells in the chunk
*/
public void update(){
Globals.profiler.beginCpuSample("ClientDrawCellManager.updateCells");
Globals.profiler.beginCpuSample("ClientDrawCellManager.update");
if(shouldUpdate && Globals.playerEntity != null){
Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
//the sets to iterate through
updatedLastFrame = true;
validCellCount = 0;
evaluationMap.clear();
//update all full res cells
FloatingChunkTreeNode<DrawCell> rootNode = this.chunkTree.getRoot();
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, HALF_RES_LOD);
Globals.profiler.beginCpuSample("ClientDrawCellManager.update - full res cells");
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, evaluationMap, HALF_RES_LOD);
Globals.profiler.endCpuSample();
if(!updatedLastFrame && !this.initialized){
this.initialized = true;
}
if(!updatedLastFrame){
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, QUARTER_RES_LOD);
Globals.profiler.beginCpuSample("ClientDrawCellManager.update - half res cells");
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, evaluationMap, QUARTER_RES_LOD);
Globals.profiler.endCpuSample();
}
if(!updatedLastFrame){
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, EIGHTH_RES_LOD);
Globals.profiler.beginCpuSample("ClientDrawCellManager.update - quarter res cells");
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, evaluationMap, EIGHTH_RES_LOD);
Globals.profiler.endCpuSample();
}
if(!updatedLastFrame){
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, SIXTEENTH_RES_LOD);
Globals.profiler.beginCpuSample("ClientDrawCellManager.update - eighth res cells");
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, evaluationMap, SIXTEENTH_RES_LOD);
Globals.profiler.endCpuSample();
}
if(!updatedLastFrame){
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, ALL_RES_LOD);
Globals.profiler.beginCpuSample("ClientDrawCellManager.update - all res cells");
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, evaluationMap, ALL_RES_LOD);
Globals.profiler.endCpuSample();
}
}
Globals.profiler.endCpuSample();
@ -183,11 +202,15 @@ public class ClientDrawCellManager {
* @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(FloatingChunkTreeNode<DrawCell> node, Vector3d playerPos, int minLeafLod){
private boolean recursivelyUpdateCells(FloatingChunkTreeNode<DrawCell> node, Vector3d playerPos, Map<FloatingChunkTreeNode<DrawCell>,Boolean> evaluationMap, int minLeafLod){
Vector3d playerRealPos = EntityUtils.getPosition(Globals.playerEntity);
boolean updated = false;
if(evaluationMap.containsKey(node)){
return false;
}
if(this.shouldSplit(playerPos, node)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.split");
//perform op
@ -205,6 +228,7 @@ public class ClientDrawCellManager {
);
DrawCell drawCell = DrawCell.generateTerrainCell(cellWorldPos);
child.convertToLeaf(drawCell);
evaluationMap.put(child,true);
});
//update neighbors
@ -226,6 +250,7 @@ public class ClientDrawCellManager {
//update neighbors
this.conditionalUpdateAdjacentNodes(newLeaf, newLeaf.getLevel());
evaluationMap.put(newLeaf,true);
Globals.profiler.endCpuSample();
updated = true;
@ -240,6 +265,7 @@ public class ClientDrawCellManager {
if(this.requestChunks(node, highResFaces)){
cell.setHasRequested(true);
}
evaluationMap.put(node,true);
Globals.profiler.endCpuSample();
updated = true;
@ -258,17 +284,21 @@ public class ClientDrawCellManager {
node.getData().setHasRequested(false);
}
}
evaluationMap.put(node,true);
Globals.profiler.endCpuSample();
updated = true;
} else if(!node.isLeaf()){
this.validCellCount++;
List<FloatingChunkTreeNode<DrawCell>> children = new LinkedList<FloatingChunkTreeNode<DrawCell>>(node.getChildren());
for(FloatingChunkTreeNode<DrawCell> child : children){
boolean childUpdate = recursivelyUpdateCells(child, playerPos, minLeafLod);
boolean childUpdate = recursivelyUpdateCells(child, playerPos, evaluationMap, minLeafLod);
if(childUpdate == true){
updated = true;
}
}
if((this.chunkTree.getMaxLevel() - node.getLevel()) < minLeafLod){
evaluationMap.put(node,true);
}
}
return updated;
}
@ -398,7 +428,7 @@ public class ClientDrawCellManager {
*/
private void conditionalUpdateAdjacentNodes(FloatingChunkTreeNode<DrawCell> node, int level){
//don't bother to check if it's a lowest-res chunk
if(this.chunkTree.getMaxLevel() - level > DrawCell.LOWEST_LOD){
if(this.chunkTree.getMaxLevel() - level > ClientDrawCellManager.FULL_RES_LOD){
return;
}
if(node.getMinBound().x - 1 >= 0){
@ -887,6 +917,36 @@ public class ClientDrawCellManager {
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 DrawCell getDrawCell(int worldX, int worldY, int worldZ){
FloatingChunkTreeNode<DrawCell> 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){
DrawCell cell = this.getDrawCell(worldX, worldY, worldZ);
if(cell != null && cell.getEntity() != null){
return PhysicsEntityUtils.containsDBody(cell.getEntity());
}
return false;
}
}

View File

@ -42,31 +42,6 @@ public class DrawCell {
float[][][] weights = new float[ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE];
int[][][] types = new int[ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE];
/**
* An invalid LOD level
*/
public static final int INVALID_LOD_LEVEL = -1;
/**
* The maximum detail LOD level
*/
public static final int FULL_DETAIL_LOD = 0;
/**
* The half detail lod level
*/
public static final int HALF_DETAIL_LOD = 1;
/**
* The lowest available LOD
*/
public static final int LOWEST_LOD = HALF_DETAIL_LOD;
/**
* The lod level of this draw cell
*/
int lodLevel = FULL_DETAIL_LOD;
/**
* Tracks whether the draw cell has requested its chunk data or not
*/
@ -139,9 +114,6 @@ public class DrawCell {
}
}
}
if(lod == INVALID_LOD_LEVEL){
throw new Error("Trying to generate invalid LOD");
}
modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(chunkData, lod, atlas, this.hasPolygons());
ClientEntityUtils.initiallyPositionEntity(modelEntity, getRealPos(), new Quaterniond());
this.setHasGenerated(true);
@ -452,23 +424,6 @@ public class DrawCell {
return true;
}
/**
* Gets the LOD level
* @return The LOD level
*/
public int getLodLevel() {
return lodLevel;
}
/**
* Sets the LOD level
* @param lodLevel The LOD level
*/
public void setLodLevel(int lodLevel) {
this.lodLevel = lodLevel;
}
/**
* Gets whether this draw cell has requested its chunk data or not
* @return true if has requested, false otherwise

View File

@ -42,7 +42,7 @@ public class ClientLoading {
/**
* The number of frames the draw cell is expected to take (minimum) to init
*/
static final int DRAW_CELL_EXPECTED_MINIMUM_FRAMES_TO_INIT = 100;
static final int DRAW_CELL_EXPECTED_MINIMUM_FRAMES_TO_INIT = 10;
protected static void loadCharacterServer(Object[] params){
@ -313,7 +313,7 @@ public class ClientLoading {
}
}
if(i < DRAW_CELL_EXPECTED_MINIMUM_FRAMES_TO_INIT){
LoggerInterface.loggerEngine.WARNING("Probably didn't block for draw cell manager initialization!");
LoggerInterface.loggerEngine.WARNING("Draw cell manager loaded exceptionally fast!");
}
}

View File

@ -40,7 +40,7 @@ public class Profiler {
}
/**
* Begins an aggregate CPU sample (must create a regular cpu sample of same type outside function this is inside of to encapsule all calls to aggregate)
* Begins an aggregate CPU sample
* @param sampleName The name of the sample
*/
public void beginAggregateCpuSample(String sampleName){
@ -49,6 +49,16 @@ public class Profiler {
}
}
/**
* Begins an recursive CPU sample
* @param sampleName The name of the sample
*/
public void beginRecursiveCpuSample(String sampleName){
if(PROFILE){
Remotery.rmt_BeginCPUSample(sampleName, Remotery.RMTSF_Recursive, null);
}
}
/**
* Begins a Root CPU sample (will assert if another sample is not ended before this one)
* @param sampleName The name of the root sample

View File

@ -64,6 +64,9 @@ public class ServerEntityUtils {
throw new Error("Trying to set server entity position to null!");
}
Realm realm = Globals.realmManager.getEntityRealm(entity);
if(position.x < 0 || position.y < 0 || position.z < 0){
throw new Error("Providing invalid location to reposition! " + position);
}
//if server, get current server data cell
ServerDataCell oldDataCell = Globals.entityDataCellMapper.getEntityDataCell(entity);
ServerDataCell newDataCell = realm.getDataCellManager().getDataCellAtPoint(position);

View File

@ -166,7 +166,7 @@ public class Scene {
* Simulates all behavior trees stored in the entity manager
*/
public void simulateBehaviorTrees(float deltaTime){
Globals.profiler.beginCpuSample("Scene.simulateBehaviorTrees");
Globals.profiler.beginAggregateCpuSample("Scene.simulateBehaviorTrees");
for(BehaviorTree tree : behaviorTreeList){
tree.simulate(deltaTime);
}

View File

@ -47,7 +47,7 @@ public class AttachUtils {
* @param cell The data cell
*/
public static void serverUpdateAttachedEntityPositions(ServerDataCell cell){
Globals.profiler.beginCpuSample("AttachUtils.serverUpdateAttachedEntityPositions");
Globals.profiler.beginAggregateCpuSample("AttachUtils.serverUpdateAttachedEntityPositions");
serverUpdateBoneAttachedEntityPositions(cell);
serverUpdateNonBoneAttachments(cell);
Globals.profiler.endCpuSample();
@ -95,7 +95,7 @@ public class AttachUtils {
* @param cell the data cell
*/
private static void serverUpdateNonBoneAttachments(ServerDataCell cell){
Globals.profiler.beginCpuSample("AttachUtils.serverUpdateNonBoneAttachments");
Globals.profiler.beginAggregateCpuSample("AttachUtils.serverUpdateNonBoneAttachments");
Matrix4d parentTransform = new Matrix4d().identity();
Vector3d position = new Vector3d();
Quaterniond rotation = new Quaterniond();

View File

@ -3,7 +3,6 @@ package electrosphere.entity.state.movement.editor;
import electrosphere.entity.state.gravity.GravityUtils;
import electrosphere.engine.Globals;
import electrosphere.entity.types.collision.CollisionObjUtils;
import electrosphere.entity.types.creature.CreatureUtils;
import electrosphere.game.data.creature.type.movement.EditorMovementSystem;
import electrosphere.entity.Entity;
@ -259,7 +258,9 @@ public class ClientEditorMovementTree implements BehaviorTree {
rotation.set(movementQuaternion);
position.set(new Vector3d(position).add(new Vector3d(movementVector).mul(velocity)));
if(serverEntity != null){
ServerEntityUtils.repositionEntity(serverEntity, new Vector3d(position));
if(position.x >= 0 && position.y >= 0 && position.z >= 0){
ServerEntityUtils.repositionEntity(serverEntity, new Vector3d(position));
}
}
GravityUtils.clientAttemptActivateGravity(parent);
@ -274,7 +275,9 @@ public class ClientEditorMovementTree implements BehaviorTree {
rotation.set(movementQuaternion);
position.set(new Vector3d(position).add(new Vector3d(movementVector).mul(velocity)));
if(serverEntity != null){
ServerEntityUtils.repositionEntity(serverEntity, new Vector3d(position));
if(position.x >= 0 && position.y >= 0 && position.z >= 0){
ServerEntityUtils.repositionEntity(serverEntity, new Vector3d(position));
}
}
GravityUtils.clientAttemptActivateGravity(parent);
@ -295,7 +298,9 @@ public class ClientEditorMovementTree implements BehaviorTree {
rotation.set(movementQuaternion);
position.set(new Vector3d(position).add(new Vector3d(movementVector).mul(velocity)));
if(serverEntity != null){
ServerEntityUtils.repositionEntity(serverEntity, new Vector3d(position));
if(position.x >= 0 && position.y >= 0 && position.z >= 0){
ServerEntityUtils.repositionEntity(serverEntity, new Vector3d(position));
}
}
GravityUtils.clientAttemptActivateGravity(parent);

View File

@ -3,9 +3,10 @@ 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.terrain.cells.DrawCell;
import electrosphere.client.terrain.cells.ClientDrawCellManager;
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.client.terrain.manager.ClientTerrainManager;
import electrosphere.collision.PhysicsEntityUtils;
@ -13,7 +14,10 @@ import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.ServerEntityUtils;
import electrosphere.entity.types.collision.CollisionObjUtils;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.meshgen.TerrainChunkModelGeneration;
import electrosphere.renderer.meshgen.TransvoxelModelGeneration;
import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData;
@ -27,7 +31,7 @@ public class TerrainChunk {
/**
* Used for generating terrain chunks
*/
static ExecutorService generationService = Executors.newFixedThreadPool(2);
static final ExecutorService generationService = Executors.newFixedThreadPool(2);
/**
* Creates a client terrain chunk based on weights and values provided
@ -44,17 +48,22 @@ public class TerrainChunk {
if(hasPolygons){
generationService.submit(() -> {
TerrainChunkData data;
if(levelOfDetail == DrawCell.FULL_DETAIL_LOD){
data = TerrainChunkModelGeneration.generateTerrainChunkData(chunkData.terrainGrid, chunkData.textureGrid);
} else {
try {
data = TransvoxelModelGeneration.generateTerrainChunkData(chunkData);
}
if(Globals.clientScene.containsEntity(rVal)){
String modelPath = ClientTerrainManager.queueTerrainGridGeneration(data, atlas);
EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath);
if(levelOfDetail == DrawCell.FULL_DETAIL_LOD){
PhysicsEntityUtils.clientAttachTerrainChunkRigidBody(rVal, data);
if(Globals.clientScene.containsEntity(rVal)){
String modelPath = ClientTerrainManager.queueTerrainGridGeneration(data, atlas);
EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath);
if(levelOfDetail == ClientDrawCellManager.FULL_RES_LOD){
PhysicsEntityUtils.clientAttachTerrainChunkRigidBody(rVal, data);
CollisionObjUtils.clientPositionCharacter(rVal, new Vector3d(EntityUtils.getPosition(rVal)), new Quaterniond());
}
} else {
LoggerInterface.loggerEngine.WARNING("Finished generating terrain polygons; however, entity has already been deleted.");
}
} catch (Error e){
LoggerInterface.loggerEngine.ERROR(e);
} catch(Exception e){
LoggerInterface.loggerEngine.ERROR(e);
}
});
}

View File

@ -14,6 +14,7 @@ import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.TerrainMessage;
import electrosphere.net.template.ClientProtocolTemplate;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
/**
* The client protocol for handling terrain messages
@ -32,8 +33,16 @@ public class TerrainProtocol implements ClientProtocolTemplate<TerrainMessage> {
case RESPONSEMETADATA:
Globals.clientWorldData = new ClientWorldData(
//Vector3f worldMinPoint, Vector3f worldMaxPoint, int dynamicInterpolationRatio, float randomDampener, int worldDiscreteSize
new Vector3f(message.getworldMinX(),0,message.getworldMinY()),
new Vector3f(message.getworldMaxX(),32,message.getworldMaxY()),
new Vector3f(
message.getworldMinX(),
0,
message.getworldMinY()
),
new Vector3f(
message.getworldMaxX(),
(int)(message.getworldSizeDiscrete() * ServerTerrainChunk.CHUNK_DIMENSION),
message.getworldMaxY()
),
message.getrandomDampener(),
message.getworldSizeDiscrete()
);

View File

@ -12,6 +12,7 @@ public class MainServerFunctions {
* Calls the main server routines that should fire each frame
*/
public static void simulate(){
Globals.profiler.beginCpuSample("MainServerFunctions.simulate");
//
//Cleanup disconnected clients
@ -21,12 +22,12 @@ public class MainServerFunctions {
//
//Synchronous player message parsing\
Globals.profiler.beginCpuSample("Server synchronous packet parsing");
Globals.profiler.beginCpuSample("MainServerFunctions.simulate - Server synchronous packet parsing");
if(Globals.server != null){
Globals.server.synchronousPacketHandling();
}
Globals.profiler.endCpuSample();
Globals.profiler.beginCpuSample("Server process synchronization messages");
Globals.profiler.beginCpuSample("MainServerFunctions.simulate - Server process synchronization messages");
if(Globals.serverSynchronizationManager != null){
Globals.serverSynchronizationManager.processMessages();
}
@ -34,7 +35,7 @@ public class MainServerFunctions {
//
//Micro simulation (ie simulating each scene on the server)
Globals.profiler.beginCpuSample("Server micro simulation");
Globals.profiler.beginCpuSample("MainServerFunctions.simulate - Server micro simulation");
LoggerInterface.loggerEngine.DEBUG_LOOP("Begin server micro simulation");
if(Globals.realmManager != null){
Globals.realmManager.simulate();
@ -42,11 +43,13 @@ public class MainServerFunctions {
//
//Macro simulation (ie simulating the larger world macro data)
LoggerInterface.loggerEngine.DEBUG_LOOP("Server macro simulation");
LoggerInterface.loggerEngine.DEBUG_LOOP("MainServerFunctions.simulate - Server macro simulation");
if(Globals.macroSimulation != null && Globals.macroSimulation.isReady()){
Globals.macroSimulation.simulate();
}
Globals.profiler.endCpuSample();
Globals.profiler.endCpuSample();
}
}

View File

@ -24,6 +24,12 @@ public class ServerContentGenerator {
*/
public static final int FOLIAGE_SEED = 0;
/**
* Adjustment applied to generated height value to approximate the height of the actual terrain.
* The voxels don't generate QUITE to the height of the heightmap, so this is applied to make the value line up better for entity placement.
*/
public static final float HEIGHT_MANUAL_ADJUSTMENT = -0.35f;
/**
* Generates content for a given data cell
* @param realm The realm
@ -55,7 +61,7 @@ public class ServerContentGenerator {
if(foliageDescriptions != null){
for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){
for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){
double height = realm.getServerWorldData().getServerTerrainManager().getElevation(worldPos.x, worldPos.z, x, z);
double height = realm.getServerWorldData().getServerTerrainManager().getElevation(worldPos.x, worldPos.z, x, z) + HEIGHT_MANUAL_ADJUSTMENT;
if(
realm.getServerWorldData().convertVoxelToRealSpace(0, worldPos.y) <= height &&
realm.getServerWorldData().convertVoxelToRealSpace(ServerTerrainChunk.CHUNK_DIMENSION, worldPos.y) > height

View File

@ -1,7 +1,6 @@
package electrosphere.server.content;
import java.util.Collection;
import java.util.List;
import org.joml.Vector3i;

View File

@ -546,6 +546,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
* Calls the simulate function on all loaded cells
*/
public void simulate(){
Globals.profiler.beginCpuSample("GriddedDataCellManager.simulate");
loadedCellsLock.acquireUninterruptibly();
for(ServerDataCell cell : this.groundDataCells.values()){
if(Globals.microSimulation != null && Globals.microSimulation.isReady()){
@ -561,6 +562,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
}
loadedCellsLock.release();
this.updatePlayerPositions();
Globals.profiler.endCpuSample();
}
@ -748,11 +750,13 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
* @param worldPosition the world position
*/
private void rebroadcastFluidChunk(Vector3i worldPosition){
Globals.profiler.beginAggregateCpuSample("GriddedDataCellManager.rebroadcastFluidChunk");
ServerDataCell cell = getCellAtWorldPosition(worldPosition);
ServerFluidChunk chunk = getFluidChunkAtPosition(worldPosition);
cell.broadcastNetworkMessage(
TerrainMessage.constructupdateFluidDataMessage(worldPosition.x, worldPosition.y, worldPosition.z, TerrainProtocol.constructFluidByteBuffer(chunk).array())
);
Globals.profiler.endCpuSample();
}
@Override

View File

@ -187,6 +187,7 @@ public class Realm {
* Tells the data cell manager to simulate all loaded cells
*/
protected void simulate(){
Globals.profiler.beginCpuSample("Realm.simulate");
//
//simulate bullet physics engine step
if(Globals.RUN_PHYSICS){
@ -209,6 +210,8 @@ public class Realm {
//clear collidable impulse lists
collisionEngine.clearCollidableImpulseLists();
chemistryEngine.clearCollidableImpulseLists();
Globals.profiler.endCpuSample();
}
/**

View File

@ -158,9 +158,11 @@ public class RealmManager {
* Simulates all realms in this manager
*/
public void simulate(){
Globals.profiler.beginCpuSample("RealmManager.simulate");
for(Realm realm : realms){
realm.simulate();
}
Globals.profiler.endCpuSample();
}
//TODO: !!URGENT!! come up with some mechanism to enforce this actually being called every time a player is added to a server data cell

View File

@ -1,6 +1,7 @@
package electrosphere.server.fluid.manager;
import electrosphere.engine.Globals;
import electrosphere.game.server.world.ServerWorldData;
import electrosphere.server.fluid.diskmap.FluidDiskMap;
import electrosphere.server.fluid.generation.FluidGenerator;
@ -270,14 +271,17 @@ public class ServerFluidManager {
* Simulates a chunk
*/
public boolean simulate(int worldX, int worldY, int worldZ){
boolean rVal = false;
Globals.profiler.beginAggregateCpuSample("ServerFluidManager.simulate");
if(simulate){
ServerFluidChunk fluidChunk = this.getChunk(worldX, worldY, worldZ);
ServerTerrainChunk terrainChunk = this.serverTerrainManager.getChunk(worldX, worldY, worldZ);
if(fluidChunk != null && terrainChunk != null && this.serverFluidSimulator != null){
return this.serverFluidSimulator.simulate(fluidChunk,terrainChunk,worldX,worldY,worldZ);
rVal = this.serverFluidSimulator.simulate(fluidChunk,terrainChunk,worldX,worldY,worldZ);
}
}
return false;
Globals.profiler.endCpuSample();
return rVal;
}
//getter for simulate

View File

@ -28,6 +28,7 @@ public class MicroSimulation {
* @param dataCell The data cell
*/
public void simulate(ServerDataCell dataCell){
Globals.profiler.beginAggregateCpuSample("MicroSimulation.simulate");
if(dataCell.isReady()){
//simulate ai
Globals.aiManager.simulate();
@ -58,6 +59,7 @@ public class MicroSimulation {
ServerCollidableTree.getServerCollidableTree(collidable).simulate((float)Globals.timekeeper.getSimFrameTime());
}
}
Globals.profiler.endCpuSample();
}
public boolean isReady(){

View File

@ -225,7 +225,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
){
GeneratedVoxel voxel = new GeneratedVoxel();
voxel.weight = (float)(surfaceHeight - flooredSurfaceHeight) * 2 - 1;
voxel.type = 1;
voxel.type = 2;
return voxel;
}

View File

@ -130,6 +130,8 @@ public class ChunkGenerationThread implements Runnable {
this.onLoad.accept(chunk);
} catch (Error e){
LoggerInterface.loggerEngine.ERROR(e);
} catch(Exception e){
LoggerInterface.loggerEngine.ERROR(e);
}
}

View File

@ -28,7 +28,7 @@ public class ServerTerrainManager {
/**
* The number of threads for chunk generation
*/
public static final int GENERATION_THREAD_POOL_SIZE = 1;
public static final int GENERATION_THREAD_POOL_SIZE = 2;
/**
* Full world discrete size
@ -76,7 +76,7 @@ public class ServerTerrainManager {
* The threadpool for chunk generation
*/
@Exclude
ExecutorService chunkExecutorService = Executors.newFixedThreadPool(GENERATION_THREAD_POOL_SIZE);
static final ExecutorService chunkExecutorService = Executors.newFixedThreadPool(GENERATION_THREAD_POOL_SIZE);
/**
* Constructor
@ -225,7 +225,7 @@ public class ServerTerrainManager {
* @return The ServerTerrainChunk
*/
public ServerTerrainChunk getChunk(int worldX, int worldY, int worldZ){
Globals.profiler.beginCpuSample("ServerTerrainManager.getChunk");
Globals.profiler.beginAggregateCpuSample("ServerTerrainManager.getChunk");
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
ServerTerrainChunk returnedChunk = null;
if(chunkCache.containsChunk(worldX,worldY,worldZ,ChunkData.NO_STRIDE)){
@ -256,7 +256,7 @@ public class ServerTerrainManager {
* @return The ServerTerrainChunk
*/
public double getElevation(int worldX, int worldZ, int chunkX, int chunkZ){
Globals.profiler.beginCpuSample("ServerTerrainManager.getChunk");
Globals.profiler.beginAggregateCpuSample("ServerTerrainManager.getChunk");
double elevation = chunkGenerator.getElevation(worldX, worldZ, chunkX, chunkZ);
Globals.profiler.endCpuSample();
return elevation;
@ -271,8 +271,8 @@ public class ServerTerrainManager {
* @param onLoad The logic to run once the chunk is available
*/
public void getChunkAsync(int worldX, int worldY, int worldZ, int stride, Consumer<ServerTerrainChunk> onLoad){
Globals.profiler.beginCpuSample("ServerTerrainManager.getChunkAsync");
this.chunkExecutorService.submit(new ChunkGenerationThread(chunkDiskMap, chunkCache, chunkGenerator, worldX, worldY, worldZ, stride, onLoad));
Globals.profiler.beginAggregateCpuSample("ServerTerrainManager.getChunkAsync");
chunkExecutorService.submit(new ChunkGenerationThread(chunkDiskMap, chunkCache, chunkGenerator, worldX, worldY, worldZ, stride, onLoad));
Globals.profiler.endCpuSample();
}
@ -326,7 +326,7 @@ public class ServerTerrainManager {
* Closes the generation threadpool
*/
public void closeThreads(){
this.chunkExecutorService.shutdownNow();
chunkExecutorService.shutdownNow();
}
}

View File

@ -42,7 +42,7 @@ public class ClientDrawCellManagerTests {
FloatingChunkTreeNode<DrawCell> node = FloatingChunkTreeNode.constructorForTests(manager.chunkTree, 1, new Vector3i(16,0,0), new Vector3i(32,16,16));
node.convertToLeaf(DrawCell.generateTerrainCell(new Vector3i(0,0,0)));
assertFalse(manager.shouldSplit(playerPos, node));
assertTrue(manager.shouldSplit(playerPos, node));
//cleanup
Main.shutdown();