Complete overhaul of foliage management
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
This commit is contained in:
parent
dc00a20bb0
commit
4d934873c3
@ -1112,6 +1112,7 @@ Remove point lights from skeleton + human
|
||||
Change grass texture
|
||||
Fix allocations on FoliageChunk child iterations
|
||||
Reduce near clip to remove flickering on far chunks
|
||||
Complete overhaul of foliage management
|
||||
|
||||
|
||||
# TODO
|
||||
|
||||
@ -115,6 +115,45 @@ public class ClientWorldData {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a real space position to its absolute voxel space equivalent
|
||||
* @param position The real space position
|
||||
* @return The absolute voxel space position ie the voxel-aligned position not clamped to the current chunk
|
||||
*/
|
||||
public Vector3i convertRealToAbsoluteVoxelSpace(Vector3d position){
|
||||
return new Vector3i(
|
||||
(int)Math.floor(position.x),
|
||||
(int)Math.floor(position.y),
|
||||
(int)Math.floor(position.z)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a absolute voxel position to its relative voxel space equivalent
|
||||
* @param position The real space position
|
||||
* @return The relative voxel space position ie the voxel-aligned position not clamped to the current chunk
|
||||
*/
|
||||
public Vector3i convertAbsoluteVoxelToRelativeVoxelSpace(Vector3i position){
|
||||
return new Vector3i(
|
||||
position.x % ServerTerrainChunk.CHUNK_DIMENSION,
|
||||
position.y % ServerTerrainChunk.CHUNK_DIMENSION,
|
||||
position.z % ServerTerrainChunk.CHUNK_DIMENSION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a absolute voxel position to its world space equivalent
|
||||
* @param position The real space position
|
||||
* @return The world space position ie the voxel-aligned position not clamped to the current chunk
|
||||
*/
|
||||
public Vector3i convertAbsoluteVoxelToWorldSpace(Vector3i position){
|
||||
return new Vector3i(
|
||||
position.x / ServerTerrainChunk.CHUNK_DIMENSION,
|
||||
position.y / ServerTerrainChunk.CHUNK_DIMENSION,
|
||||
position.z / ServerTerrainChunk.CHUNK_DIMENSION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a world space vector to a real space vector
|
||||
* @param position The world space vector
|
||||
|
||||
@ -87,8 +87,8 @@ public class ClientSimulation {
|
||||
Globals.profiler.endCpuSample();
|
||||
//
|
||||
//update foliage
|
||||
if(Globals.clientFoliageManager != null){
|
||||
Globals.clientFoliageManager.update();
|
||||
if(Globals.foliageCellManager != null){
|
||||
Globals.foliageCellManager.update();
|
||||
}
|
||||
//
|
||||
//targeting crosshair
|
||||
|
||||
@ -241,8 +241,8 @@ public class DrawCellManager {
|
||||
List<DrawCellFace> higherLODFace = null;
|
||||
keyCellMap.get(targetKey).generateDrawableEntity(atlas,0,higherLODFace);
|
||||
|
||||
//evaluate for foliage
|
||||
Globals.clientFoliageManager.evaluateChunk(worldPos);
|
||||
// //evaluate for foliage
|
||||
// Globals.clientFoliageManager.evaluateChunk(worldPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,701 @@
|
||||
package electrosphere.client.terrain.foliage;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import org.joml.Matrix4d;
|
||||
import org.joml.Quaterniond;
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import electrosphere.client.entity.camera.CameraEntityUtils;
|
||||
import electrosphere.client.terrain.cache.ChunkData;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.ClientEntityUtils;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityCreationUtils;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.entity.btree.BehaviorTree;
|
||||
import electrosphere.renderer.OpenGLState;
|
||||
import electrosphere.renderer.RenderPipelineState;
|
||||
import electrosphere.renderer.actor.instance.TextureInstancedActor;
|
||||
import electrosphere.renderer.buffer.HomogenousUniformBuffer.HomogenousBufferTypes;
|
||||
import electrosphere.renderer.buffer.ShaderAttribute;
|
||||
import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData;
|
||||
import electrosphere.server.terrain.manager.ServerTerrainChunk;
|
||||
import electrosphere.util.ds.octree.WorldOctTree.WorldOctTreeNode;
|
||||
import electrosphere.util.math.GeomUtils;
|
||||
|
||||
/**
|
||||
* A single foliagecell - contains an entity that has a physics mesh and potentially graphics
|
||||
*/
|
||||
public class FoliageCell {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Wiggle room in number of entries
|
||||
*/
|
||||
static final int BUFFER_WIGGLE_ROOM = 200;
|
||||
|
||||
/**
|
||||
* The interval to space along
|
||||
*/
|
||||
static final int TARGET_FOLIAGE_SPACING = 50;
|
||||
|
||||
/**
|
||||
* The target number of foliage to place per cell
|
||||
*/
|
||||
static final int TARGET_FOLIAGE_PER_CELL = TARGET_FOLIAGE_SPACING * TARGET_FOLIAGE_SPACING + BUFFER_WIGGLE_ROOM;
|
||||
|
||||
/**
|
||||
* The length of the ray to ground test with
|
||||
*/
|
||||
static final float RAY_LENGTH = 1.0f;
|
||||
|
||||
/**
|
||||
* The height above the chunk to start from when sampling downwards
|
||||
*/
|
||||
static final float SAMPLE_START_HEIGHT = 0.5f;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* </p>
|
||||
* A lot of these are x 4 to account for size of float
|
||||
* 3 x 4 for position
|
||||
* 2 x 4 for euler rotation
|
||||
*
|
||||
*
|
||||
* eventually:
|
||||
* grass type
|
||||
* color
|
||||
* wind characteristics?
|
||||
*/
|
||||
static final int SINGLE_FOLIAGE_DATA_SIZE_BYTES = 3 * 4 + 2 * 4;
|
||||
|
||||
/**
|
||||
* The map of all attributes for instanced foliage
|
||||
*/
|
||||
static final Map<ShaderAttribute,HomogenousBufferTypes> attributes = new HashMap<ShaderAttribute,HomogenousBufferTypes>();
|
||||
|
||||
/**
|
||||
* Model matrix shader attribute
|
||||
*/
|
||||
static ShaderAttribute modelMatrixAttribute;
|
||||
|
||||
/**
|
||||
* The list of voxel type ids that should have grass generated on top of them
|
||||
*/
|
||||
static final List<Integer> grassGeneratingVoxelIds = new ArrayList<Integer>();
|
||||
|
||||
//set attributes
|
||||
static {
|
||||
int[] attributeIndices = new int[]{
|
||||
5,6,7,8
|
||||
};
|
||||
modelMatrixAttribute = new ShaderAttribute(attributeIndices);
|
||||
attributes.put(modelMatrixAttribute,HomogenousBufferTypes.MAT4F);
|
||||
|
||||
//set grass generating voxel ids
|
||||
grassGeneratingVoxelIds.add(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vertex shader path
|
||||
*/
|
||||
protected static final String vertexPath = "Shaders/entities/foliage/foliage.vs";
|
||||
|
||||
/**
|
||||
* Fragment shader path
|
||||
*/
|
||||
protected static final String fragmentPath = "Shaders/entities/foliage/foliage.fs";
|
||||
|
||||
/**
|
||||
* Random for finding new positions for foliage
|
||||
*/
|
||||
Random placementRandomizer = new Random();
|
||||
|
||||
|
||||
/**
|
||||
* The position of the foliage cell in world coordinates
|
||||
*/
|
||||
Vector3i worldPos;
|
||||
|
||||
/**
|
||||
* The position of this cell voxel-wise within its chunk
|
||||
*/
|
||||
Vector3i voxelPos;
|
||||
|
||||
|
||||
/**
|
||||
* The LOD of the foliage cell
|
||||
*/
|
||||
int lod;
|
||||
|
||||
/**
|
||||
* The main entity for the cell
|
||||
*/
|
||||
Entity modelEntity;
|
||||
|
||||
/**
|
||||
* The data for generating the visuals
|
||||
*/
|
||||
TransvoxelChunkData chunkData;
|
||||
|
||||
/**
|
||||
* Tracks whether the foliage cell has requested its chunk data or not
|
||||
*/
|
||||
boolean hasRequested = false;
|
||||
|
||||
/**
|
||||
* Tracks whether the foliage cell has generated its entity or not
|
||||
*/
|
||||
boolean hasGenerated = false;
|
||||
|
||||
|
||||
/**
|
||||
* Tracks whether this foliage 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
|
||||
*/
|
||||
FoliageCell notifyTarget = null;
|
||||
|
||||
/**
|
||||
* The number of cells that have alerted this one
|
||||
*/
|
||||
int generationAlertCount = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Private constructor
|
||||
*/
|
||||
private FoliageCell(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a foliagecell object
|
||||
*/
|
||||
public static FoliageCell generateTerrainCell(
|
||||
Vector3i voxelAbsPos,
|
||||
int lod
|
||||
){
|
||||
FoliageCell rVal = new FoliageCell();
|
||||
rVal.lod = lod;
|
||||
rVal.worldPos = Globals.clientWorldData.convertAbsoluteVoxelToWorldSpace(voxelAbsPos);
|
||||
rVal.voxelPos = Globals.clientWorldData.convertAbsoluteVoxelToRelativeVoxelSpace(voxelAbsPos);
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a homogenous foliagecell object
|
||||
*/
|
||||
public static FoliageCell generateHomogenousTerrainCell(
|
||||
Vector3i voxelAbsPos,
|
||||
int lod
|
||||
){
|
||||
FoliageCell rVal = new FoliageCell();
|
||||
rVal.lod = lod;
|
||||
rVal.worldPos = Globals.clientWorldData.convertAbsoluteVoxelToWorldSpace(voxelAbsPos);
|
||||
rVal.voxelPos = Globals.clientWorldData.convertAbsoluteVoxelToRelativeVoxelSpace(voxelAbsPos);
|
||||
rVal.hasGenerated = true;
|
||||
rVal.homogenous = true;
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a drawable entity based on this chunk
|
||||
*/
|
||||
public void generateDrawableEntity(int lod){
|
||||
boolean success = true;
|
||||
if(chunkData == null){
|
||||
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
|
||||
worldPos.x,
|
||||
worldPos.y,
|
||||
worldPos.z,
|
||||
0
|
||||
);
|
||||
if(currentChunk == null){
|
||||
success = false;
|
||||
} else {
|
||||
this.homogenous = currentChunk.getHomogenousValue() != ChunkData.NOT_HOMOGENOUS;
|
||||
success = true;
|
||||
}
|
||||
if(!success){
|
||||
this.setFailedGenerationAttempts(this.getFailedGenerationAttempts() + 1);
|
||||
return;
|
||||
}
|
||||
this.chunkData = new TransvoxelChunkData(currentChunk.getVoxelWeight(), currentChunk.getVoxelType(), 0);
|
||||
}
|
||||
this.generate();
|
||||
this.setHasGenerated(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates the foliage cell
|
||||
*/
|
||||
protected void generate(){
|
||||
boolean shouldGenerate = false;
|
||||
if(voxelPos.y + 1 >= ServerTerrainChunk.CHUNK_DIMENSION){
|
||||
return;
|
||||
}
|
||||
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos,ChunkData.NO_STRIDE);
|
||||
if(data == null){
|
||||
return;
|
||||
}
|
||||
//get foliage types supported
|
||||
List<String> foliageTypesSupported = new LinkedList<String>();
|
||||
boolean airAbove = data.getType(voxelPos.x,voxelPos.y+1,voxelPos.z) == 0;
|
||||
int scale = (int)Math.pow(2,lod);
|
||||
for(int x = 0; x < scale; x++){
|
||||
for(int y = 0; y < scale; y++){
|
||||
for(int z = 0; z < scale; z++){
|
||||
List<String> currentList = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(data.getType(voxelPos)).getAmbientFoliage();
|
||||
if(currentList == null){
|
||||
continue;
|
||||
}
|
||||
foliageTypesSupported.addAll(currentList);
|
||||
airAbove = data.getType(voxelPos.x,voxelPos.y+1,voxelPos.z) == 0;
|
||||
if(foliageTypesSupported != null && foliageTypesSupported.size() > 0 && airAbove){
|
||||
shouldGenerate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(shouldGenerate){
|
||||
//create entity
|
||||
this.modelEntity = EntityCreationUtils.createClientSpatialEntity();
|
||||
FoliageModel.clientCreateFoliageChunkEntity(foliageTypesSupported,scale,this.modelEntity,this.getRealPos(),worldPos,voxelPos,null,null);
|
||||
//get type
|
||||
// String foliageTypeName = foliageTypesSupported.get(placementRandomizer.nextInt() % foliageTypesSupported.size());
|
||||
// FoliageType foliageType = Globals.gameConfigCurrent.getFoliageMap().getFoliage(foliageTypeName);
|
||||
|
||||
// //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 + this.insertBlades(x, y, z, floatBufferView, data);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // drawCount = drawCount + this.insertBlades(0, 0, 0, floatBufferView, data);
|
||||
// if(drawCount > 0){
|
||||
// buffer.position(0);
|
||||
// buffer.limit(TARGET_FOLIAGE_PER_CELL * SINGLE_FOLIAGE_DATA_SIZE_BYTES);
|
||||
// //construct data texture
|
||||
// Texture dataTexture = new Texture(Globals.renderingEngine.getOpenGLState(),buffer,SINGLE_FOLIAGE_DATA_SIZE_BYTES / 4,TARGET_FOLIAGE_PER_CELL);
|
||||
|
||||
// //create entity
|
||||
// this.modelEntity = EntityCreationUtils.createClientSpatialEntity();
|
||||
|
||||
// TextureInstancedActor.attachTextureInstancedActor(this.modelEntity, foliageType.getGraphicsTemplate().getModel().getPath(), vertexPath, fragmentPath, dataTexture, drawCount);
|
||||
// ClientEntityUtils.initiallyPositionEntity(this.modelEntity, this.getRealPos(), new Quaterniond());
|
||||
// EntityUtils.getScale(this.modelEntity).set(1,1,1);
|
||||
// //add ambient foliage behavior tree
|
||||
// AmbientFoliage.attachAmbientFoliageTree(this.modelEntity, 1.0f, foliageType.getGrowthModel().getGrowthRate());
|
||||
// }
|
||||
} else {
|
||||
this.homogenous = true;
|
||||
}
|
||||
this.hasGenerated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert blades of grass into the entity
|
||||
* @param vX the x offset of the voxel
|
||||
* @param vY the y offset of the voxel
|
||||
* @param vZ the z offset of the voxel
|
||||
* @param floatBufferView the gpu data buffer
|
||||
* @param chunkData the chunk data
|
||||
* @return the number of blades of grass added
|
||||
*/
|
||||
protected int insertBlades(int vX, int vY, int vZ, FloatBuffer floatBufferView, ChunkData chunkData){
|
||||
int rVal = 0;
|
||||
|
||||
//get positions offset
|
||||
Vector3d voxelRealPos = new Vector3d(this.getRealPos()).add(vX,vY,vZ);
|
||||
Vector3i currVoxelPos = new Vector3i(this.voxelPos).add(vX,vY,vZ);
|
||||
|
||||
int scale = (int)Math.pow(2,lod);
|
||||
|
||||
//check that the current voxel even supports foliage
|
||||
boolean shouldGenerate = false;
|
||||
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) == AIR_VOXEL_ID;
|
||||
if(foliageTypesSupported != null && airAbove){
|
||||
shouldGenerate = true;
|
||||
}
|
||||
}
|
||||
if(shouldGenerate){
|
||||
//construct simple grid to place foliage on
|
||||
Vector3d sample_00 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add(-0.5,SAMPLE_START_HEIGHT,-0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_01 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add(-0.5,SAMPLE_START_HEIGHT, 0), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_02 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add(-0.5,SAMPLE_START_HEIGHT, 0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_10 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0,SAMPLE_START_HEIGHT,-0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_11 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0,SAMPLE_START_HEIGHT, 0), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_12 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0,SAMPLE_START_HEIGHT, 0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_20 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0.5,SAMPLE_START_HEIGHT,-0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_21 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0.5,SAMPLE_START_HEIGHT, 0), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_22 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0.5,SAMPLE_START_HEIGHT, 0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
//get the heights of each sample
|
||||
float height_11 = (float)(sample_11 != null ? sample_11.y : 0);
|
||||
float height_00 = (float)(sample_00 != null ? sample_00.y : height_11);
|
||||
float height_01 = (float)(sample_01 != null ? sample_01.y : height_11);
|
||||
float height_02 = (float)(sample_02 != null ? sample_02.y : height_11);
|
||||
float height_10 = (float)(sample_10 != null ? sample_10.y : height_11);
|
||||
float height_12 = (float)(sample_12 != null ? sample_12.y : height_11);
|
||||
float height_20 = (float)(sample_20 != null ? sample_20.y : height_11);
|
||||
float height_21 = (float)(sample_21 != null ? sample_21.y : height_11);
|
||||
float height_22 = (float)(sample_22 != null ? sample_22.y : height_11);
|
||||
//each height is in real world coordinates that are absolute
|
||||
//when rendering, there's already a y offset for the center of the field of grass (based on the model matrix)
|
||||
//so when offseting the position of the blade of grass RELATIVE to the overall instance being drawn, need to subtract the real world coordinates of the overall instance
|
||||
//in other words realPos SPECIFICALLY for the y dimension, for x and z you don't need to worry about it
|
||||
|
||||
//if we don't find data for the center sample, can't place grass so don't create entity
|
||||
if(sample_11 != null){
|
||||
//generate positions to place
|
||||
for(int x = 0; x < TARGET_FOLIAGE_SPACING; x=x+scale){
|
||||
for(int z = 0; z < TARGET_FOLIAGE_SPACING; z=z+scale){
|
||||
//get position to place
|
||||
double rand1 = placementRandomizer.nextDouble();
|
||||
double rand2 = placementRandomizer.nextDouble();
|
||||
double relativePositionOnGridX = x / (1.0 * TARGET_FOLIAGE_SPACING) + rand1 / TARGET_FOLIAGE_SPACING;
|
||||
double relativePositionOnGridZ = z / (1.0 * TARGET_FOLIAGE_SPACING) + rand2 / TARGET_FOLIAGE_SPACING;
|
||||
double offsetX = relativePositionOnGridX - 0.5;
|
||||
double offsetZ = relativePositionOnGridZ - 0.5;
|
||||
//determine quadrant we're placing in
|
||||
double offsetY = 0;
|
||||
boolean addBlade = false;
|
||||
if(relativePositionOnGridX >=0.5){
|
||||
if(relativePositionOnGridZ >= 0.5){
|
||||
relativePositionOnGridX = relativePositionOnGridX - 0.5;
|
||||
relativePositionOnGridZ = relativePositionOnGridZ - 0.5;
|
||||
relativePositionOnGridX /= 0.5;
|
||||
relativePositionOnGridZ /= 0.5;
|
||||
// System.out.println(relativePositionOnGridX + " " + relativePositionOnGridZ);
|
||||
//if we have heights for all four surrounding spots, interpolate for y value
|
||||
if(sample_11 != null && sample_12 != null && sample_21 != null && sample_22 != null){
|
||||
offsetY =
|
||||
height_11 * (1-relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_12 * (1-relativePositionOnGridX) * ( relativePositionOnGridZ) +
|
||||
height_21 * ( relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_22 * ( relativePositionOnGridX) * ( relativePositionOnGridZ);
|
||||
addBlade = true;
|
||||
}
|
||||
} else {
|
||||
relativePositionOnGridX = relativePositionOnGridX - 0.5;
|
||||
relativePositionOnGridX /= 0.5;
|
||||
relativePositionOnGridZ /= 0.5;
|
||||
//if we have heights for all four surrounding spots, interpolate for y value
|
||||
if(sample_10 != null && sample_11 != null && sample_20 != null && sample_21 != null){
|
||||
offsetY =
|
||||
height_10 * (1-relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_11 * (1-relativePositionOnGridX) * ( relativePositionOnGridZ) +
|
||||
height_20 * ( relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_21 * ( relativePositionOnGridX) * ( relativePositionOnGridZ);
|
||||
addBlade = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(relativePositionOnGridZ >= 0.5){
|
||||
relativePositionOnGridZ = relativePositionOnGridZ - 0.5;
|
||||
relativePositionOnGridX /= 0.5;
|
||||
relativePositionOnGridZ /= 0.5;
|
||||
//if we have heights for all four surrounding spots, interpolate for y value
|
||||
if(sample_01 != null && sample_02 != null && sample_11 != null && sample_12 != null){
|
||||
offsetY =
|
||||
height_01 * (1-relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_02 * (1-relativePositionOnGridX) * ( relativePositionOnGridZ) +
|
||||
height_11 * ( relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_12 * ( relativePositionOnGridX) * ( relativePositionOnGridZ);
|
||||
addBlade = true;
|
||||
}
|
||||
} else {
|
||||
relativePositionOnGridX /= 0.5;
|
||||
relativePositionOnGridZ /= 0.5;
|
||||
//if we have heights for all four surrounding spots, interpolate for y value
|
||||
if(sample_00 != null && sample_01 != null && sample_10 != null && sample_11 != null){
|
||||
offsetY =
|
||||
height_00 * (1-relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_01 * (1-relativePositionOnGridX) * ( relativePositionOnGridZ) +
|
||||
height_10 * ( relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_11 * ( relativePositionOnGridX) * ( relativePositionOnGridZ);
|
||||
addBlade = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(addBlade){
|
||||
//convert y to relative to chunk
|
||||
offsetY = offsetY - this.getRealPos().y;
|
||||
double rotVar = placementRandomizer.nextDouble() * Math.PI * 2;
|
||||
double rotVar2 = placementRandomizer.nextDouble();
|
||||
if(floatBufferView.limit() >= floatBufferView.position() + SINGLE_FOLIAGE_DATA_SIZE_BYTES / 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws all entities in the foliage cell
|
||||
*/
|
||||
protected void draw(){
|
||||
if(this.modelEntity != null){
|
||||
Matrix4d modelMatrix = new Matrix4d();
|
||||
Vector3f cameraCenter = CameraEntityUtils.getCameraCenter(Globals.playerCamera);
|
||||
|
||||
RenderPipelineState renderPipelineState = Globals.renderingEngine.getRenderPipelineState();
|
||||
OpenGLState openGLState = Globals.renderingEngine.getOpenGLState();
|
||||
|
||||
Vector3d realPosition = this.getRealPos();
|
||||
Vector3f cameraModifiedPosition = new Vector3f((float)realPosition.x,(float)realPosition.y,(float)realPosition.z).sub(cameraCenter);
|
||||
//frustum check entire cell
|
||||
int size = (int)Math.pow(2,this.lod);
|
||||
boolean shouldRender = renderPipelineState.getFrustumIntersection().testSphere(
|
||||
(float)(cameraModifiedPosition.x + size / 2.0),
|
||||
(float)(cameraModifiedPosition.y + size / 2.0),
|
||||
(float)(cameraModifiedPosition.z + size / 2.0),
|
||||
(float)(size)
|
||||
);
|
||||
if(shouldRender){
|
||||
//disable frustum check and instead perform at cell level
|
||||
boolean currentFrustumCheckState = renderPipelineState.shouldFrustumCheck();
|
||||
renderPipelineState.setFrustumCheck(false);
|
||||
Vector3d grassPosition = EntityUtils.getPosition(modelEntity);
|
||||
Quaterniond grassRotation = EntityUtils.getRotation(modelEntity);
|
||||
TextureInstancedActor actor = TextureInstancedActor.getTextureInstancedActor(modelEntity);
|
||||
|
||||
if(actor != null){
|
||||
modelMatrix = modelMatrix.identity();
|
||||
modelMatrix.translate(cameraModifiedPosition);
|
||||
modelMatrix.rotate(new Quaterniond(grassRotation));
|
||||
modelMatrix.scale(new Vector3d(EntityUtils.getScale(modelEntity)));
|
||||
actor.applySpatialData(modelMatrix,grassPosition);
|
||||
|
||||
|
||||
//draw
|
||||
actor.draw(renderPipelineState, openGLState);
|
||||
renderPipelineState.setFrustumCheck(currentFrustumCheckState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the real-space position of the foliage cell
|
||||
* @return the real-space position
|
||||
*/
|
||||
protected Vector3d getRealPos(){
|
||||
return new Vector3d(
|
||||
worldPos.x * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET + voxelPos.x,
|
||||
worldPos.y * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET + voxelPos.y,
|
||||
worldPos.z * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET + voxelPos.z
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world-space position of the foliage cell
|
||||
* @return the world-space position
|
||||
*/
|
||||
protected Vector3i getWorldPos(){
|
||||
return new Vector3i(worldPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a target foliage cell to notify once this one has completed generating its model
|
||||
* @param notifyTarget The target to notify
|
||||
*/
|
||||
public void registerNotificationTarget(FoliageCell notifyTarget){
|
||||
this.notifyTarget = notifyTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerts this foliage 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 foliage cell 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 foliage cell
|
||||
* @param source The source foliage cell
|
||||
*/
|
||||
public void transferChunkData(FoliageCell source){
|
||||
this.chunkData = source.chunkData;
|
||||
this.homogenous = source.homogenous;
|
||||
this.hasRequested = source.hasRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this foliage cell has requested its chunk data or not
|
||||
* @return true if has requested, false otherwise
|
||||
*/
|
||||
public boolean hasRequested() {
|
||||
return hasRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this foliage 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 foliage cell has generated its entity or not
|
||||
* @return true if has generated, false otherwise
|
||||
*/
|
||||
public boolean hasGenerated() {
|
||||
return hasGenerated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this foliage 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 foliage cell is homogenous or not
|
||||
* @param hasGenerated true if is homogenous, false otherwise
|
||||
*/
|
||||
public void setHomogenous(boolean homogenous) {
|
||||
this.homogenous = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this foliage 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<FoliageCell> node, int distCache){
|
||||
if(cachedMinDistance != INVALID_DIST_CACHE && distCache < lod){
|
||||
return cachedMinDistance;
|
||||
} else {
|
||||
this.cachedMinDistance = GeomUtils.getMinSquaredDistanceAABB(worldPos, node.getMinBound(), node.getMaxBound());
|
||||
return cachedMinDistance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,876 @@
|
||||
package electrosphere.client.terrain.foliage;
|
||||
|
||||
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.engine.Globals;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.game.data.foliage.type.FoliageType;
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.server.terrain.manager.ServerTerrainChunk;
|
||||
import electrosphere.util.ds.octree.WorldOctTree;
|
||||
import electrosphere.util.ds.octree.WorldOctTree.WorldOctTreeNode;
|
||||
import electrosphere.util.math.GeomUtils;
|
||||
|
||||
/**
|
||||
* Manages foliage cells on the client
|
||||
*/
|
||||
public class FoliageCellManager {
|
||||
|
||||
/**
|
||||
* Number of times to try updating per frame. Lower this to reduce lag but slow down terrain 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 foliage at full resolution
|
||||
*/
|
||||
public static final double FULL_RES_DIST = 16 * 16;
|
||||
|
||||
/**
|
||||
* The distance for half resolution
|
||||
*/
|
||||
public static final double HALF_RES_DIST = 20 * 20;
|
||||
|
||||
/**
|
||||
* The distance for quarter resolution
|
||||
*/
|
||||
public static final double QUARTER_RES_DIST = 24 * 24;
|
||||
|
||||
/**
|
||||
* 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 = 48 * 48;
|
||||
|
||||
/**
|
||||
* Lod value for a full res chunk
|
||||
*/
|
||||
public static final int FULL_RES_LOD = 0;
|
||||
|
||||
/**
|
||||
* Lod value for a half res chunk
|
||||
*/
|
||||
public static final int HALF_RES_LOD = 1;
|
||||
|
||||
/**
|
||||
* Lod value for a quarter res chunk
|
||||
*/
|
||||
public static final int QUARTER_RES_LOD = 2;
|
||||
|
||||
/**
|
||||
* Lod value for a eighth res chunk
|
||||
*/
|
||||
public static final int EIGHTH_RES_LOD = 3;
|
||||
|
||||
/**
|
||||
* Lod value for a sixteenth res chunk
|
||||
*/
|
||||
public static final int SIXTEENTH_RES_LOD = 4;
|
||||
|
||||
/**
|
||||
* Lod value for evaluating all lod levels
|
||||
*/
|
||||
public static final int ALL_RES_LOD = 5;
|
||||
|
||||
/**
|
||||
* The octree holding all the chunks to evaluate
|
||||
*/
|
||||
WorldOctTree<FoliageCell> chunkTree;
|
||||
|
||||
/**
|
||||
* Tracks what nodes have been evaluated this frame -- used to deduplicate evaluation calls
|
||||
*/
|
||||
Map<WorldOctTreeNode<FoliageCell>,Boolean> evaluationMap = new HashMap<WorldOctTreeNode<FoliageCell>,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 foliage cell manager should update or not
|
||||
*/
|
||||
boolean shouldUpdate = true;
|
||||
|
||||
/**
|
||||
* 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 worldDim The size of the world in chunks
|
||||
*/
|
||||
public FoliageCellManager(int worldDim){
|
||||
this.chunkTree = new WorldOctTree<FoliageCell>(
|
||||
new Vector3i(0,0,0),
|
||||
new Vector3i(worldDim * ServerTerrainChunk.CHUNK_DIMENSION, worldDim * ServerTerrainChunk.CHUNK_DIMENSION, worldDim * ServerTerrainChunk.CHUNK_DIMENSION)
|
||||
);
|
||||
this.chunkTree.getRoot().setData(FoliageCell.generateTerrainCell(new Vector3i(0,0,0), chunkTree.getMaxLevel()));
|
||||
this.worldDim = worldDim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits the foliage cell data
|
||||
*/
|
||||
public void init(){
|
||||
//queue ambient foliage models
|
||||
for(FoliageType foliageType : Globals.gameConfigCurrent.getFoliageMap().getFoliageList()){
|
||||
if(foliageType.getTokens().contains(FoliageType.TOKEN_AMBIENT)){
|
||||
Globals.assetManager.addModelPathToQueue(foliageType.getGraphicsTemplate().getModel().getPath());
|
||||
Globals.assetManager.addShaderToQueue(FoliageCell.vertexPath, FoliageCell.fragmentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all cells in the chunk
|
||||
*/
|
||||
public void update(){
|
||||
Globals.profiler.beginCpuSample("FoliageCellManager.update");
|
||||
if(shouldUpdate && Globals.playerEntity != null){
|
||||
Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
|
||||
Vector3i absVoxelPos = Globals.clientWorldData.convertRealToAbsoluteVoxelSpace(playerPos);
|
||||
int distCache = this.getDistCache(this.lastPlayerPos, absVoxelPos);
|
||||
this.lastPlayerPos.set(absVoxelPos);
|
||||
//the sets to iterate through
|
||||
updatedLastFrame = true;
|
||||
validCellCount = 0;
|
||||
evaluationMap.clear();
|
||||
//update all full res cells
|
||||
WorldOctTreeNode<FoliageCell> rootNode = this.chunkTree.getRoot();
|
||||
Globals.profiler.beginCpuSample("FoliageCellManager.update - full res cells");
|
||||
updatedLastFrame = this.recursivelyUpdateCells(rootNode, absVoxelPos, evaluationMap, SIXTEENTH_RES_LOD, distCache);
|
||||
Globals.profiler.endCpuSample();
|
||||
if(!updatedLastFrame && !this.initialized){
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
Globals.profiler.endCpuSample();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively update child nodes
|
||||
* @param node The root node
|
||||
* @param absVoxelPos 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<FoliageCell> node, Vector3i absVoxelPos, Map<WorldOctTreeNode<FoliageCell>,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(absVoxelPos, node, distCache)){
|
||||
Globals.profiler.beginCpuSample("FoliageCellManager.split");
|
||||
//perform op
|
||||
WorldOctTreeNode<FoliageCell> container = chunkTree.split(node);
|
||||
FoliageCell containerCell = FoliageCell.generateTerrainCell(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
|
||||
);
|
||||
FoliageCell foliageCell = FoliageCell.generateTerrainCell(cellWorldPos,this.chunkTree.getMaxLevel() - child.getLevel());
|
||||
foliageCell.registerNotificationTarget(node.getData());
|
||||
child.setLeaf(true);
|
||||
child.setData(foliageCell);
|
||||
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(absVoxelPos, node, minLeafLod, distCache)){
|
||||
Globals.profiler.beginCpuSample("FoliageCellManager.request");
|
||||
|
||||
//calculate what to request
|
||||
FoliageCell 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(absVoxelPos, node, minLeafLod, distCache)){
|
||||
Globals.profiler.beginCpuSample("FoliageCellManager.generate");
|
||||
int lodLevel = this.getLODLevel(node);
|
||||
|
||||
if(this.containsDataToGenerate(node)){
|
||||
node.getData().generateDrawableEntity(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(absVoxelPos, node, distCache)) {
|
||||
this.join(node);
|
||||
updated = true;
|
||||
} else {
|
||||
this.validCellCount++;
|
||||
List<WorldOctTreeNode<FoliageCell>> children = node.getChildren();
|
||||
boolean isHomogenous = true;
|
||||
boolean fullyGenerated = true;
|
||||
for(int i = 0; i < 8; i++){
|
||||
WorldOctTreeNode<FoliageCell> child = children.get(i);
|
||||
boolean childUpdate = this.recursivelyUpdateCells(child, absVoxelPos, evaluationMap, minLeafLod, distCache);
|
||||
if(childUpdate == true){
|
||||
updated = true;
|
||||
}
|
||||
if(!child.getData().hasGenerated()){
|
||||
fullyGenerated = false;
|
||||
}
|
||||
if(!child.getData().isHomogenous()){
|
||||
isHomogenous = false;
|
||||
}
|
||||
}
|
||||
WorldOctTreeNode<FoliageCell> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw all foliage cells
|
||||
*/
|
||||
public void draw(){
|
||||
this.recursivelyDraw(this.chunkTree.getRoot());
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws all foliage cells recursively
|
||||
* @param node The current node
|
||||
*/
|
||||
private void recursivelyDraw(WorldOctTreeNode<FoliageCell> node){
|
||||
if(node.getChildren() != null && node.getChildren().size() > 0){
|
||||
for(int i = 0; i < 8; i++){
|
||||
this.recursivelyDraw(node.getChildren().get(i));
|
||||
}
|
||||
}
|
||||
node.getData().draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<FoliageCell> 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 SIXTEENTH_RES_LOD + 2;
|
||||
}
|
||||
if(
|
||||
lastPlayerPos.x / 8 != currentPlayerPos.x / 8 || lastPlayerPos.z / 8 != currentPlayerPos.z / 8 || lastPlayerPos.z / 8 != currentPlayerPos.z / 8
|
||||
){
|
||||
return SIXTEENTH_RES_LOD + 1;
|
||||
}
|
||||
if(
|
||||
lastPlayerPos.x / 4 != currentPlayerPos.x / 4 || lastPlayerPos.z / 4 != currentPlayerPos.z / 4 || lastPlayerPos.z / 4 != currentPlayerPos.z / 4
|
||||
){
|
||||
return SIXTEENTH_RES_LOD;
|
||||
}
|
||||
if(
|
||||
lastPlayerPos.x / 2 != currentPlayerPos.x / 2 || lastPlayerPos.z / 2 != currentPlayerPos.z / 2 || lastPlayerPos.z / 2 != currentPlayerPos.z / 2
|
||||
){
|
||||
return EIGHTH_RES_LOD;
|
||||
}
|
||||
if(
|
||||
lastPlayerPos.x != currentPlayerPos.x || lastPlayerPos.z != currentPlayerPos.z || lastPlayerPos.z != currentPlayerPos.z
|
||||
){
|
||||
return QUARTER_RES_LOD;
|
||||
}
|
||||
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<FoliageCell> 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 foliage calls
|
||||
return
|
||||
node.canSplit() &&
|
||||
(node.getLevel() != this.chunkTree.getMaxLevel()) &&
|
||||
!node.getData().isHomogenous() &&
|
||||
(node.getParent() != null || node == this.chunkTree.getRoot()) &&
|
||||
(
|
||||
(
|
||||
node.getLevel() < this.chunkTree.getMaxLevel() - SIXTEENTH_RES_LOD &&
|
||||
this.getMinDistance(pos, node, distCache) <= SIXTEENTH_RES_DIST
|
||||
)
|
||||
||
|
||||
(
|
||||
node.getLevel() < this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD &&
|
||||
this.getMinDistance(pos, node, distCache) <= EIGHTH_RES_DIST
|
||||
)
|
||||
||
|
||||
(
|
||||
node.getLevel() < this.chunkTree.getMaxLevel() - QUARTER_RES_LOD &&
|
||||
this.getMinDistance(pos, node, distCache) <= QUARTER_RES_DIST
|
||||
)
|
||||
// ||
|
||||
// (
|
||||
// node.getLevel() < this.chunkTree.getMaxLevel() - HALF_RES_LOD &&
|
||||
// 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 foliage cell
|
||||
* @param node The node to consider
|
||||
* @return -1 if outside of render range, -1 if the node is not a valid foliage cell leaf, otherwise returns the LOD level
|
||||
*/
|
||||
private int getLODLevel(WorldOctTreeNode<FoliageCell> 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<FoliageCell> node, int level){
|
||||
//don't bother to check if it's a lowest-res chunk
|
||||
if(this.chunkTree.getMaxLevel() - level > FoliageCellManager.FULL_RES_LOD){
|
||||
return;
|
||||
}
|
||||
if(node.getMinBound().x - 1 >= 0){
|
||||
WorldOctTreeNode<FoliageCell> 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<FoliageCell> 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<FoliageCell> 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<FoliageCell> 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<FoliageCell> 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<FoliageCell> 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<FoliageCell> 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 foliage calls
|
||||
return
|
||||
node.getLevel() > 0 &&
|
||||
(node.getLevel() != this.chunkTree.getMaxLevel()) &&
|
||||
(
|
||||
(
|
||||
node.getLevel() == this.chunkTree.getMaxLevel() - HALF_RES_LOD &&
|
||||
this.getMinDistance(pos, node, distCache) > FULL_RES_DIST
|
||||
)
|
||||
||
|
||||
(
|
||||
node.getLevel() == this.chunkTree.getMaxLevel() - QUARTER_RES_LOD &&
|
||||
this.getMinDistance(pos, node, distCache) > HALF_RES_DIST
|
||||
)
|
||||
||
|
||||
(
|
||||
node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD &&
|
||||
this.getMinDistance(pos, node, distCache) > QUARTER_RES_DIST
|
||||
)
|
||||
||
|
||||
(
|
||||
node.getLevel() == this.chunkTree.getMaxLevel() - SIXTEENTH_RES_LOD &&
|
||||
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<FoliageCell> join(WorldOctTreeNode<FoliageCell> node){
|
||||
Globals.profiler.beginCpuSample("FoliageCellManager.join");
|
||||
|
||||
//queue destructions prior to join -- the join operator clears all children on node
|
||||
this.recursivelyDestroy(node);
|
||||
|
||||
//perform op
|
||||
FoliageCell newLeafCell = FoliageCell.generateTerrainCell(node.getMinBound(),node.getData().lod);
|
||||
WorldOctTreeNode<FoliageCell> 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<FoliageCell> 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() - HALF_RES_LOD
|
||||
&&
|
||||
this.getMinDistance(pos, node, distCache) <= QUARTER_RES_DIST
|
||||
)
|
||||
||
|
||||
(
|
||||
node.getLevel() == this.chunkTree.getMaxLevel() - QUARTER_RES_LOD
|
||||
&&
|
||||
this.getMinDistance(pos, node, distCache) <= EIGHTH_RES_DIST
|
||||
)
|
||||
||
|
||||
(
|
||||
node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD
|
||||
&&
|
||||
this.getMinDistance(pos, node, distCache) <= SIXTEENTH_RES_DIST
|
||||
)
|
||||
||
|
||||
(
|
||||
node.getLevel() == this.chunkTree.getMaxLevel() - SIXTEENTH_RES_LOD
|
||||
&&
|
||||
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<FoliageCell> 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() - HALF_RES_LOD
|
||||
&&
|
||||
this.getMinDistance(pos, node, distCache) <= QUARTER_RES_DIST
|
||||
)
|
||||
||
|
||||
(
|
||||
node.getLevel() == this.chunkTree.getMaxLevel() - QUARTER_RES_LOD
|
||||
&&
|
||||
this.getMinDistance(pos, node, distCache) <= EIGHTH_RES_DIST
|
||||
)
|
||||
||
|
||||
(
|
||||
node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD
|
||||
&&
|
||||
this.getMinDistance(pos, node, distCache) <= SIXTEENTH_RES_DIST
|
||||
)
|
||||
||
|
||||
(
|
||||
node.getLevel() == this.chunkTree.getMaxLevel() - SIXTEENTH_RES_LOD
|
||||
&&
|
||||
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<FoliageCell> 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<FoliageCell> node){
|
||||
if(node.getChildren().size() > 0){
|
||||
for(WorldOctTreeNode<FoliageCell> 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<FoliageCell> node){
|
||||
if(!node.getData().hasGenerated()){
|
||||
for(WorldOctTreeNode<FoliageCell> 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 foliage cell as updateable
|
||||
* @param worldX The world x position
|
||||
* @param worldY The world y position
|
||||
* @param worldZ The world z position
|
||||
*/
|
||||
public void markUpdateable(float worldX, float worldY, float worldZ){
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests all chunks for a given foliage cell
|
||||
* @param cell The cell
|
||||
* @return true if all cells were successfully requested, false otherwise
|
||||
*/
|
||||
private boolean requestChunks(WorldOctTree.WorldOctTreeNode<FoliageCell> node){
|
||||
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.clientTerrainManager.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, 0)
|
||||
){
|
||||
//client should request chunk data from server for each chunk necessary to create the model
|
||||
LoggerInterface.loggerNetworking.DEBUG("(Client) Send Request for terrain at " + worldPos);
|
||||
if(!Globals.clientTerrainManager.requestChunk(worldPos.x, worldPos.y, worldPos.z, 0)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all chunk data required to generate this foliage cell is present
|
||||
* @param node The node
|
||||
* @param highResFace The higher resolution face of a not-full-resolution chunk. Null if the chunk is max resolution or there is no higher resolution face for the current chunk
|
||||
* @return true if all data is available, false otherwise
|
||||
*/
|
||||
private boolean containsDataToGenerate(WorldOctTree.WorldOctTreeNode<FoliageCell> node){
|
||||
FoliageCell cell = node.getData();
|
||||
Vector3i worldPos = cell.getWorldPos();
|
||||
if(!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, 0)){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the foliage 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 foliage 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 foliage 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<FoliageCell> 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<FoliageCell>> children = new LinkedList<WorldOctTreeNode<FoliageCell>>(node.getChildren());
|
||||
for(WorldOctTreeNode<FoliageCell> 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 foliage cell manager has initialized or not
|
||||
* @return true if it has initialized, false otherwise
|
||||
*/
|
||||
public boolean isInitialized(){
|
||||
return this.initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the foliage 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 foliage cell if it exists, null otherwise
|
||||
*/
|
||||
public FoliageCell getFoliageCell(int worldX, int worldY, int worldZ){
|
||||
WorldOctTreeNode<FoliageCell> node = this.chunkTree.search(new Vector3i(worldX,worldY,worldZ), false);
|
||||
if(node != null){
|
||||
return node.getData();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,338 @@
|
||||
package electrosphere.client.terrain.foliage;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.joml.Quaterniond;
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector3i;
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import electrosphere.client.terrain.cache.ChunkData;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.engine.assetmanager.queue.QueuedTexture;
|
||||
import electrosphere.entity.ClientEntityUtils;
|
||||
import electrosphere.entity.Entity;
|
||||
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.actor.instance.TextureInstancedActor;
|
||||
import electrosphere.server.terrain.manager.ServerTerrainChunk;
|
||||
|
||||
/**
|
||||
* Generates a foliage model
|
||||
*/
|
||||
public class FoliageModel {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Wiggle room in number of entries
|
||||
*/
|
||||
static final int BUFFER_WIGGLE_ROOM = 200;
|
||||
|
||||
/**
|
||||
* The interval to space along
|
||||
*/
|
||||
static final int TARGET_FOLIAGE_SPACING = 50;
|
||||
|
||||
/**
|
||||
* The target number of foliage to place per cell
|
||||
*/
|
||||
static final int TARGET_FOLIAGE_PER_CELL = TARGET_FOLIAGE_SPACING * TARGET_FOLIAGE_SPACING + BUFFER_WIGGLE_ROOM;
|
||||
|
||||
/**
|
||||
* The length of the ray to ground test with
|
||||
*/
|
||||
static final float RAY_LENGTH = 1.0f;
|
||||
|
||||
/**
|
||||
* The height above the chunk to start from when sampling downwards
|
||||
*/
|
||||
static final float SAMPLE_START_HEIGHT = 0.5f;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* </p>
|
||||
* A lot of these are x 4 to account for size of float
|
||||
* 3 x 4 for position
|
||||
* 2 x 4 for euler rotation
|
||||
*
|
||||
*
|
||||
* eventually:
|
||||
* grass type
|
||||
* color
|
||||
* wind characteristics?
|
||||
*/
|
||||
static final int SINGLE_FOLIAGE_DATA_SIZE_BYTES = 3 * 4 + 2 * 4;
|
||||
|
||||
/**
|
||||
* Vertex shader path
|
||||
*/
|
||||
protected static final String vertexPath = "Shaders/entities/foliage/foliage.vs";
|
||||
|
||||
/**
|
||||
* Fragment shader path
|
||||
*/
|
||||
protected static final String fragmentPath = "Shaders/entities/foliage/foliage.fs";
|
||||
|
||||
/**
|
||||
* Used for generating foliage cells
|
||||
*/
|
||||
static final ExecutorService generationService = Executors.newFixedThreadPool(2);
|
||||
|
||||
/**
|
||||
* Creates a client foliage chunk based on weights and values provided
|
||||
* @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 hasFoliage true if the chunk has polygons to generate a model with, false otherwise
|
||||
* @return The terrain chunk entity
|
||||
*/
|
||||
public static Entity clientCreateFoliageChunkEntity(
|
||||
List<String> foliageTypesSupported,
|
||||
int scale,
|
||||
Entity modelEntity,
|
||||
Vector3d realPos,
|
||||
Vector3i worldPos,
|
||||
Vector3i voxelPos,
|
||||
FoliageCell notifyTarget,
|
||||
Entity toDelete
|
||||
){
|
||||
Globals.profiler.beginAggregateCpuSample("FoliageModel.clientCreateFoliageChunkEntity");
|
||||
|
||||
Entity rVal = EntityCreationUtils.createClientSpatialEntity();
|
||||
|
||||
generationService.submit(() -> {
|
||||
try {
|
||||
Random placementRandomizer = new Random();
|
||||
//get type
|
||||
String foliageTypeName = foliageTypesSupported.get(0);
|
||||
FoliageType foliageType = Globals.gameConfigCurrent.getFoliageMap().getFoliage(foliageTypeName);
|
||||
|
||||
//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++){
|
||||
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos,ChunkData.NO_STRIDE);
|
||||
if(data == null){
|
||||
continue;
|
||||
}
|
||||
drawCount = drawCount + FoliageModel.insertBlades(
|
||||
realPos, voxelPos,
|
||||
scale, placementRandomizer,
|
||||
x, y, z,
|
||||
floatBufferView, data
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(drawCount > 0){
|
||||
buffer.position(0);
|
||||
buffer.limit(TARGET_FOLIAGE_PER_CELL * SINGLE_FOLIAGE_DATA_SIZE_BYTES);
|
||||
//construct data texture
|
||||
QueuedTexture queuedAsset = new QueuedTexture(buffer,SINGLE_FOLIAGE_DATA_SIZE_BYTES / 4,TARGET_FOLIAGE_PER_CELL);
|
||||
Globals.assetManager.queuedAsset(queuedAsset);
|
||||
|
||||
TextureInstancedActor.attachTextureInstancedActor(modelEntity, foliageType.getGraphicsTemplate().getModel().getPath(), vertexPath, fragmentPath, queuedAsset, drawCount);
|
||||
ClientEntityUtils.initiallyPositionEntity(modelEntity, realPos, new Quaterniond());
|
||||
EntityUtils.getScale(modelEntity).set(1,1,1);
|
||||
//add ambient foliage behavior tree
|
||||
AmbientFoliage.attachAmbientFoliageTree(modelEntity, 1.0f, foliageType.getGrowthModel().getGrowthRate());
|
||||
}
|
||||
} catch (Error e){
|
||||
LoggerInterface.loggerEngine.ERROR(e);
|
||||
} catch(Exception e){
|
||||
LoggerInterface.loggerEngine.ERROR(e);
|
||||
}
|
||||
});
|
||||
|
||||
Globals.profiler.endCpuSample();
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert blades of grass into the entity
|
||||
* @param vX the x offset of the voxel
|
||||
* @param vY the y offset of the voxel
|
||||
* @param vZ the z offset of the voxel
|
||||
* @param floatBufferView the gpu data buffer
|
||||
* @param chunkData the chunk data
|
||||
* @return the number of blades of grass added
|
||||
*/
|
||||
protected static int insertBlades(
|
||||
Vector3d realPos, Vector3i voxelPos,
|
||||
int scale, Random placementRandomizer,
|
||||
int vX, int vY, int vZ,
|
||||
FloatBuffer floatBufferView, ChunkData chunkData
|
||||
){
|
||||
int rVal = 0;
|
||||
|
||||
//get positions offset
|
||||
Vector3d voxelRealPos = new Vector3d(realPos).add(vX,vY,vZ);
|
||||
Vector3i currVoxelPos = new Vector3i(voxelPos).add(vX,vY,vZ);
|
||||
|
||||
//check that the current voxel even supports foliage
|
||||
boolean shouldGenerate = false;
|
||||
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) == AIR_VOXEL_ID;
|
||||
if(foliageTypesSupported != null && airAbove){
|
||||
shouldGenerate = true;
|
||||
}
|
||||
}
|
||||
if(shouldGenerate){
|
||||
//construct simple grid to place foliage on
|
||||
Vector3d sample_00 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add(-0.5,SAMPLE_START_HEIGHT,-0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_01 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add(-0.5,SAMPLE_START_HEIGHT, 0), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_02 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add(-0.5,SAMPLE_START_HEIGHT, 0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_10 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0,SAMPLE_START_HEIGHT,-0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_11 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0,SAMPLE_START_HEIGHT, 0), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_12 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0,SAMPLE_START_HEIGHT, 0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_20 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0.5,SAMPLE_START_HEIGHT,-0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_21 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0.5,SAMPLE_START_HEIGHT, 0), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
Vector3d sample_22 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0.5,SAMPLE_START_HEIGHT, 0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
||||
//get the heights of each sample
|
||||
float height_11 = (float)(sample_11 != null ? sample_11.y : 0);
|
||||
float height_00 = (float)(sample_00 != null ? sample_00.y : height_11);
|
||||
float height_01 = (float)(sample_01 != null ? sample_01.y : height_11);
|
||||
float height_02 = (float)(sample_02 != null ? sample_02.y : height_11);
|
||||
float height_10 = (float)(sample_10 != null ? sample_10.y : height_11);
|
||||
float height_12 = (float)(sample_12 != null ? sample_12.y : height_11);
|
||||
float height_20 = (float)(sample_20 != null ? sample_20.y : height_11);
|
||||
float height_21 = (float)(sample_21 != null ? sample_21.y : height_11);
|
||||
float height_22 = (float)(sample_22 != null ? sample_22.y : height_11);
|
||||
//each height is in real world coordinates that are absolute
|
||||
//when rendering, there's already a y offset for the center of the field of grass (based on the model matrix)
|
||||
//so when offseting the position of the blade of grass RELATIVE to the overall instance being drawn, need to subtract the real world coordinates of the overall instance
|
||||
//in other words realPos SPECIFICALLY for the y dimension, for x and z you don't need to worry about it
|
||||
|
||||
//if we don't find data for the center sample, can't place grass so don't create entity
|
||||
if(sample_11 != null){
|
||||
//generate positions to place
|
||||
for(int x = 0; x < TARGET_FOLIAGE_SPACING; x=x+scale){
|
||||
for(int z = 0; z < TARGET_FOLIAGE_SPACING; z=z+scale){
|
||||
//get position to place
|
||||
double rand1 = placementRandomizer.nextDouble();
|
||||
double rand2 = placementRandomizer.nextDouble();
|
||||
double relativePositionOnGridX = x / (1.0 * TARGET_FOLIAGE_SPACING) + rand1 / TARGET_FOLIAGE_SPACING;
|
||||
double relativePositionOnGridZ = z / (1.0 * TARGET_FOLIAGE_SPACING) + rand2 / TARGET_FOLIAGE_SPACING;
|
||||
double offsetX = relativePositionOnGridX - 0.5;
|
||||
double offsetZ = relativePositionOnGridZ - 0.5;
|
||||
//determine quadrant we're placing in
|
||||
double offsetY = 0;
|
||||
boolean addBlade = false;
|
||||
if(relativePositionOnGridX >=0.5){
|
||||
if(relativePositionOnGridZ >= 0.5){
|
||||
relativePositionOnGridX = relativePositionOnGridX - 0.5;
|
||||
relativePositionOnGridZ = relativePositionOnGridZ - 0.5;
|
||||
relativePositionOnGridX /= 0.5;
|
||||
relativePositionOnGridZ /= 0.5;
|
||||
// System.out.println(relativePositionOnGridX + " " + relativePositionOnGridZ);
|
||||
//if we have heights for all four surrounding spots, interpolate for y value
|
||||
if(sample_11 != null && sample_12 != null && sample_21 != null && sample_22 != null){
|
||||
offsetY =
|
||||
height_11 * (1-relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_12 * (1-relativePositionOnGridX) * ( relativePositionOnGridZ) +
|
||||
height_21 * ( relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_22 * ( relativePositionOnGridX) * ( relativePositionOnGridZ);
|
||||
addBlade = true;
|
||||
}
|
||||
} else {
|
||||
relativePositionOnGridX = relativePositionOnGridX - 0.5;
|
||||
relativePositionOnGridX /= 0.5;
|
||||
relativePositionOnGridZ /= 0.5;
|
||||
//if we have heights for all four surrounding spots, interpolate for y value
|
||||
if(sample_10 != null && sample_11 != null && sample_20 != null && sample_21 != null){
|
||||
offsetY =
|
||||
height_10 * (1-relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_11 * (1-relativePositionOnGridX) * ( relativePositionOnGridZ) +
|
||||
height_20 * ( relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_21 * ( relativePositionOnGridX) * ( relativePositionOnGridZ);
|
||||
addBlade = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(relativePositionOnGridZ >= 0.5){
|
||||
relativePositionOnGridZ = relativePositionOnGridZ - 0.5;
|
||||
relativePositionOnGridX /= 0.5;
|
||||
relativePositionOnGridZ /= 0.5;
|
||||
//if we have heights for all four surrounding spots, interpolate for y value
|
||||
if(sample_01 != null && sample_02 != null && sample_11 != null && sample_12 != null){
|
||||
offsetY =
|
||||
height_01 * (1-relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_02 * (1-relativePositionOnGridX) * ( relativePositionOnGridZ) +
|
||||
height_11 * ( relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_12 * ( relativePositionOnGridX) * ( relativePositionOnGridZ);
|
||||
addBlade = true;
|
||||
}
|
||||
} else {
|
||||
relativePositionOnGridX /= 0.5;
|
||||
relativePositionOnGridZ /= 0.5;
|
||||
//if we have heights for all four surrounding spots, interpolate for y value
|
||||
if(sample_00 != null && sample_01 != null && sample_10 != null && sample_11 != null){
|
||||
offsetY =
|
||||
height_00 * (1-relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_01 * (1-relativePositionOnGridX) * ( relativePositionOnGridZ) +
|
||||
height_10 * ( relativePositionOnGridX) * (1-relativePositionOnGridZ) +
|
||||
height_11 * ( relativePositionOnGridX) * ( relativePositionOnGridZ);
|
||||
addBlade = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(addBlade){
|
||||
//convert y to relative to chunk
|
||||
offsetY = offsetY - realPos.y;
|
||||
double rotVar = placementRandomizer.nextDouble() * Math.PI * 2;
|
||||
double rotVar2 = placementRandomizer.nextDouble();
|
||||
if(floatBufferView.limit() >= floatBufferView.position() + SINGLE_FOLIAGE_DATA_SIZE_BYTES / 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the model generation threads
|
||||
*/
|
||||
public static void haltThreads(){
|
||||
generationService.shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
@ -523,7 +523,8 @@ public class PhysicsEntityUtils {
|
||||
collisionEngine.getCollidables();
|
||||
throw new Error("Collision engine collidables are null!");
|
||||
}
|
||||
for(Collidable collidable : collidableList){
|
||||
for(int i = 0; i < collidableList.size(); i++){
|
||||
Collidable collidable = collidableList.get(i);
|
||||
Entity entity = collidable.getParent();
|
||||
DBody body = PhysicsEntityUtils.getDBody(entity);
|
||||
if(body != null && body.isEnabled() && !body.isKinematic()){
|
||||
|
||||
@ -17,13 +17,13 @@ import electrosphere.client.chemistry.ClientChemistryCollisionCallback;
|
||||
import electrosphere.client.entity.particle.ParticleService;
|
||||
import electrosphere.client.fluid.cells.FluidCellManager;
|
||||
import electrosphere.client.fluid.manager.ClientFluidManager;
|
||||
import electrosphere.client.foliagemanager.ClientFoliageManager;
|
||||
import electrosphere.client.player.ClientPlayerData;
|
||||
import electrosphere.client.scene.ClientSceneWrapper;
|
||||
import electrosphere.client.scene.ClientWorldData;
|
||||
import electrosphere.client.sim.ClientSimulation;
|
||||
import electrosphere.client.terrain.cells.ClientDrawCellManager;
|
||||
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
|
||||
import electrosphere.client.terrain.foliage.FoliageCellManager;
|
||||
import electrosphere.client.terrain.manager.ClientTerrainManager;
|
||||
import electrosphere.client.ui.menu.WindowUtils;
|
||||
import electrosphere.collision.CollisionEngine;
|
||||
@ -343,7 +343,8 @@ public class Globals {
|
||||
public static InstanceManager clientInstanceManager = new InstanceManager();
|
||||
|
||||
//client side foliage manager
|
||||
public static ClientFoliageManager clientFoliageManager;
|
||||
// public static ClientFoliageManager clientFoliageManager;
|
||||
public static FoliageCellManager foliageCellManager;
|
||||
|
||||
//client world data
|
||||
public static ClientWorldData clientWorldData;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package electrosphere.engine.assetmanager.queue;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.renderer.texture.Texture;
|
||||
@ -19,6 +20,21 @@ public class QueuedTexture implements QueuedAsset {
|
||||
//data to be loaded
|
||||
BufferedImage data;
|
||||
|
||||
/**
|
||||
* The byte buffer
|
||||
*/
|
||||
ByteBuffer buffer;
|
||||
|
||||
/**
|
||||
* Width of the image
|
||||
*/
|
||||
int width = -1;
|
||||
|
||||
/**
|
||||
* Height of the image
|
||||
*/
|
||||
int height = -1;
|
||||
|
||||
|
||||
/**
|
||||
* Creates the queued texture object
|
||||
@ -28,9 +44,25 @@ public class QueuedTexture implements QueuedAsset {
|
||||
this.data = image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the queued texture object
|
||||
* @param buffer The data to buffer
|
||||
* @param width The width of the buffer
|
||||
* @param height The height of the buffer
|
||||
*/
|
||||
public QueuedTexture(ByteBuffer buffer, int width, int height){
|
||||
this.buffer = buffer;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
texture = new Texture(Globals.renderingEngine.getOpenGLState(), data);
|
||||
if(data != null){
|
||||
texture = new Texture(Globals.renderingEngine.getOpenGLState(), data);
|
||||
} else if(buffer != null){
|
||||
texture = new Texture(Globals.renderingEngine.getOpenGLState(),buffer,width,height);
|
||||
}
|
||||
hasLoaded = true;
|
||||
}
|
||||
|
||||
@ -46,6 +78,30 @@ public class QueuedTexture implements QueuedAsset {
|
||||
public Texture getTexture(){
|
||||
return texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the buffer data
|
||||
* @return The buffer data
|
||||
*/
|
||||
public ByteBuffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the width of the buffer
|
||||
* @return The width
|
||||
*/
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the height of the buffer
|
||||
* @return The height
|
||||
*/
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -8,9 +8,9 @@ import org.joml.Vector3f;
|
||||
import electrosphere.client.entity.camera.CameraEntityUtils;
|
||||
import electrosphere.client.entity.crosshair.Crosshair;
|
||||
import electrosphere.client.fluid.cells.FluidCellManager;
|
||||
import electrosphere.client.foliagemanager.ClientFoliageManager;
|
||||
import electrosphere.client.sim.ClientSimulation;
|
||||
import electrosphere.client.terrain.cells.ClientDrawCellManager;
|
||||
import electrosphere.client.terrain.foliage.FoliageCellManager;
|
||||
import electrosphere.client.ui.menu.MenuGenerators;
|
||||
import electrosphere.client.ui.menu.WindowStrings;
|
||||
import electrosphere.client.ui.menu.WindowUtils;
|
||||
@ -91,10 +91,10 @@ public class ClientLoading {
|
||||
Globals.controlHandler.hintUpdateControlState(ControlHandler.ControlsState.NO_INPUT);
|
||||
//initialize the "real" objects simulation
|
||||
initClientSimulation();
|
||||
//init foliage manager
|
||||
initFoliageManager();
|
||||
//initialize the cell manager (client)
|
||||
initDrawCellManager(true);
|
||||
//init foliage manager
|
||||
initFoliageManager();
|
||||
//init the fluid cell manager
|
||||
initFluidCellManager(true);
|
||||
//initialize the basic graphical entities of the world (skybox, camera)
|
||||
@ -369,8 +369,9 @@ public class ClientLoading {
|
||||
* Starts up the foliage manager
|
||||
*/
|
||||
private static void initFoliageManager(){
|
||||
Globals.clientFoliageManager = new ClientFoliageManager();
|
||||
Globals.clientFoliageManager.start();
|
||||
Globals.foliageCellManager = new FoliageCellManager(Globals.clientWorldData.getWorldDiscreteSize());
|
||||
Globals.foliageCellManager.init();
|
||||
// Globals.foliageCellManager.start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import java.util.List;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import electrosphere.client.terrain.foliage.FoliageModel;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.engine.loadingthreads.LoadingThread;
|
||||
import electrosphere.engine.threads.LabeledThread.ThreadLabel;
|
||||
@ -124,6 +125,7 @@ public class ThreadManager {
|
||||
* Halds all terrain chunk threads
|
||||
*/
|
||||
TerrainChunk.haltThreads();
|
||||
FoliageModel.haltThreads();
|
||||
|
||||
//
|
||||
//interrupt all threads
|
||||
|
||||
@ -113,7 +113,7 @@ public class TerrainProtocol implements ClientProtocolTemplate<TerrainMessage> {
|
||||
Globals.clientDrawCellManager.markUpdateable(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z);
|
||||
}
|
||||
}
|
||||
Globals.clientFoliageManager.evaluateChunk(worldPos);
|
||||
// Globals.clientFoliageManager.evaluateChunk(worldPos);
|
||||
}
|
||||
} break;
|
||||
case SENDFLUIDDATA: {
|
||||
|
||||
@ -4,6 +4,7 @@ import org.joml.Matrix4d;
|
||||
import org.joml.Vector3d;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.engine.assetmanager.queue.QueuedTexture;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityDataStrings;
|
||||
import electrosphere.renderer.OpenGLState;
|
||||
@ -27,6 +28,16 @@ public class TextureInstancedActor {
|
||||
//the draw count of the texture instanced actor
|
||||
int drawCount;
|
||||
|
||||
/**
|
||||
* The queued texture
|
||||
*/
|
||||
QueuedTexture queuedTexture;
|
||||
|
||||
/**
|
||||
* Set the queued texture pointer to the material
|
||||
*/
|
||||
boolean setQueuedTexturePointer = false;
|
||||
|
||||
//shader paths
|
||||
String vertexShaderPath;
|
||||
String fragmentShaderPath;
|
||||
@ -44,6 +55,19 @@ public class TextureInstancedActor {
|
||||
this.fragmentShaderPath = fragmentShaderPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instanced actor
|
||||
* @param modelPath The path of the model this actor uses
|
||||
*/
|
||||
protected TextureInstancedActor(String modelPath, String vertexShaderPath, String fragmentShaderPath, QueuedTexture dataTexture, int drawCount){
|
||||
this.modelPath = modelPath;
|
||||
this.material = new Material();
|
||||
this.queuedTexture = dataTexture;
|
||||
this.drawCount = drawCount;
|
||||
this.vertexShaderPath = vertexShaderPath;
|
||||
this.fragmentShaderPath = fragmentShaderPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a TextureInstancedActor to an entity
|
||||
* @param parent The entity
|
||||
@ -54,6 +78,17 @@ public class TextureInstancedActor {
|
||||
TextureInstancedActor newActor = new TextureInstancedActor(modelPath, vertexShaderPath, fragmentShaderPath, dataTexture, drawCount);
|
||||
parent.putData(EntityDataStrings.TEXTURE_INSTANCED_ACTOR, newActor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a TextureInstancedActor to an entity
|
||||
* @param parent The entity
|
||||
* @param modelPath The path to the model for this instanced actor
|
||||
* @param dataTexture The data texture containing data for this actor
|
||||
*/
|
||||
public static void attachTextureInstancedActor(Entity parent, String modelPath, String vertexShaderPath, String fragmentShaderPath, QueuedTexture dataTexture, int drawCount){
|
||||
TextureInstancedActor newActor = new TextureInstancedActor(modelPath, vertexShaderPath, fragmentShaderPath, dataTexture, drawCount);
|
||||
parent.putData(EntityDataStrings.TEXTURE_INSTANCED_ACTOR, newActor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the instanced actor. Should be called normally in a loop as if this was a regular actor.
|
||||
@ -63,7 +98,21 @@ public class TextureInstancedActor {
|
||||
public void draw(RenderPipelineState renderPipelineState, OpenGLState openGLState){
|
||||
Model model = Globals.assetManager.fetchModel(modelPath);
|
||||
VisualShader shader = Globals.assetManager.fetchShader(vertexShaderPath, fragmentShaderPath);
|
||||
if(model != null && shader != null){
|
||||
if(queuedTexture != null && !setQueuedTexturePointer && queuedTexture.getTexture() != null){
|
||||
this.material.setTexturePointer(queuedTexture.getTexture().getTexturePointer());
|
||||
setQueuedTexturePointer = true;
|
||||
}
|
||||
if(
|
||||
model != null &&
|
||||
shader != null &&
|
||||
(
|
||||
queuedTexture == null ||
|
||||
(
|
||||
queuedTexture != null &&
|
||||
queuedTexture.getTexture() != null
|
||||
)
|
||||
)
|
||||
){
|
||||
//setup render pipeline
|
||||
boolean instancedState = renderPipelineState.getInstanced();
|
||||
boolean materialState = renderPipelineState.getUseMaterial();
|
||||
|
||||
@ -89,7 +89,7 @@ public class MainContentPipeline implements RenderPipeline {
|
||||
currentActor.draw(renderPipelineState,openGLState);
|
||||
}
|
||||
}
|
||||
Globals.clientFoliageManager.draw();
|
||||
Globals.foliageCellManager.draw();
|
||||
for(Entity currentEntity : Globals.clientScene.getEntitiesWithTag(EntityTags.DRAW_INSTANCED)){
|
||||
Vector3d position = EntityUtils.getPosition(currentEntity);
|
||||
if(shouldDrawSolidPass(currentEntity)){
|
||||
|
||||
Loading…
Reference in New Issue
Block a user