688 lines
27 KiB
Java
688 lines
27 KiB
Java
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.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.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
|
|
*/
|
|
long 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();
|
|
}
|
|
|
|
|
|
/**
|
|
* Generates the foliage cell
|
|
*/
|
|
protected void generate(){
|
|
boolean shouldGenerate = false;
|
|
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 = true;
|
|
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++){
|
|
if(voxelPos.y + y >= ServerTerrainChunk.CHUNK_DIMENSION){
|
|
continue;
|
|
}
|
|
List<String> currentList = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(data.getType(new Vector3i(voxelPos).add(x,y,z))).getAmbientFoliage();
|
|
if(currentList == null){
|
|
continue;
|
|
}
|
|
foliageTypesSupported.addAll(currentList);
|
|
airAbove = data.getType(voxelPos.x + x,voxelPos.y + y + 1,voxelPos.z + z) == 0;
|
|
if(foliageTypesSupported != null && foliageTypesSupported.size() > 0 && airAbove){
|
|
shouldGenerate = true;
|
|
break;
|
|
}
|
|
}
|
|
if(shouldGenerate){
|
|
break;
|
|
}
|
|
}
|
|
if(shouldGenerate){
|
|
break;
|
|
}
|
|
}
|
|
if(shouldGenerate){
|
|
Entity oldEntity = this.modelEntity;
|
|
//create entity
|
|
this.modelEntity = FoliageModel.clientCreateFoliageChunkEntity(foliageTypesSupported,scale,this.getRealPos(),worldPos,voxelPos,notifyTarget,oldEntity);
|
|
} else {
|
|
if(this.modelEntity != null){
|
|
ClientEntityUtils.destroyEntity(this.modelEntity);
|
|
this.modelEntity = null;
|
|
}
|
|
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();
|
|
Vector3d cameraCenter = CameraEntityUtils.getCameraCenter(Globals.playerCamera);
|
|
|
|
RenderPipelineState renderPipelineState = Globals.renderingEngine.getRenderPipelineState();
|
|
OpenGLState openGLState = Globals.renderingEngine.getOpenGLState();
|
|
|
|
Vector3d realPosition = this.getRealPos();
|
|
Vector3d cameraModifiedPosition = new Vector3d(realPosition).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;
|
|
}
|
|
|
|
/**
|
|
* Ejects the chunk data
|
|
*/
|
|
public void ejectChunkData(){
|
|
this.chunkData = null;
|
|
}
|
|
|
|
/**
|
|
* 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 long getMinDistance(Vector3i worldPos, WorldOctTreeNode<FoliageCell> node, int distCache){
|
|
if(cachedMinDistance != INVALID_DIST_CACHE && distCache < lod){
|
|
return cachedMinDistance;
|
|
} else {
|
|
double dist = GeomUtils.approxMinDistanceAABB(worldPos, node.getMinBound(), node.getMaxBound());
|
|
if(Double.isFinite(dist)){
|
|
this.cachedMinDistance = (long)dist;
|
|
} else {
|
|
this.cachedMinDistance = GeomUtils.REALLY_BIG_NUMBER;
|
|
}
|
|
return cachedMinDistance;
|
|
}
|
|
}
|
|
|
|
|
|
}
|