522 lines
26 KiB
Java
522 lines
26 KiB
Java
package electrosphere.client.foliagemanager;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.DoubleBuffer;
|
|
import java.nio.FloatBuffer;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Random;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import org.joml.Quaterniond;
|
|
import org.joml.Vector3d;
|
|
import org.joml.Vector3f;
|
|
import org.joml.Vector3i;
|
|
import org.lwjgl.BufferUtils;
|
|
import org.lwjgl.system.MemoryUtil;
|
|
|
|
import electrosphere.client.terrain.cache.ChunkData;
|
|
import electrosphere.engine.Globals;
|
|
import electrosphere.entity.Entity;
|
|
import electrosphere.entity.EntityCreationUtils;
|
|
import electrosphere.entity.EntityDataStrings;
|
|
import electrosphere.entity.EntityTags;
|
|
import electrosphere.entity.EntityUtils;
|
|
import electrosphere.entity.state.foliage.AmbientFoliage;
|
|
import electrosphere.game.data.foliage.type.FoliageType;
|
|
import electrosphere.renderer.actor.instance.TextureInstancedActor;
|
|
import electrosphere.renderer.buffer.ShaderAttribute;
|
|
import electrosphere.renderer.buffer.HomogenousUniformBuffer.HomogenousBufferTypes;
|
|
import electrosphere.renderer.texture.Texture;
|
|
|
|
/**
|
|
* Manages ambient foliage (grass, small plants, etc) that should be shown, typically instanced
|
|
*/
|
|
public class ClientFoliageManager {
|
|
|
|
//Random for finding new positions for foliage
|
|
Random placementRandomizer = new Random();
|
|
|
|
//Used to prevent concurrent usage of grassEntities set
|
|
boolean ready = false;
|
|
|
|
//The list of voxel type ids that should have grass generated on top of them
|
|
static final List<Integer> grassGeneratingVoxelIds = new ArrayList<Integer>();
|
|
|
|
//FoliageCells that are active and have foliage that is being drawn
|
|
Set<FoliageCell> activeCells = new HashSet<FoliageCell>();
|
|
//map of position-based key to foliage cell at the position
|
|
Map<String,FoliageCell> locationCellMap = new HashMap<String,FoliageCell>();
|
|
//The maximum distance a cell can be away from the player before being destroyed
|
|
static final float CELL_DISTANCE_MAX = 5f;
|
|
|
|
//The maximum number of foliage cells
|
|
|
|
static final int CELL_COUNT_MAX = 1000;
|
|
//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;
|
|
//size of a single item of foliage in the texture buffer
|
|
/*
|
|
* 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;
|
|
//Stores a list of all locations that are currently invalid which map to
|
|
//the amount of frames that must pass before they are considered valid to evaluate
|
|
Map<String,Integer> locationEvaluationCooldownMap = new ConcurrentHashMap<String,Integer>();
|
|
//The number of frames that must pass before a cell can be reevaluated for foliage placement
|
|
static final int EVALUATION_COOLDOWN = 100;
|
|
|
|
|
|
|
|
|
|
//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;
|
|
|
|
//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);
|
|
}
|
|
|
|
//shader paths
|
|
static final String vertexPath = "Shaders/foliage/foliage.vs";
|
|
static final String fragmentPath = "Shaders/foliage/foliage.fs";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Starts up the foliage manager
|
|
*/
|
|
public void start(){
|
|
//queue ambient foliage models
|
|
for(FoliageType foliageType : Globals.gameConfigCurrent.getFoliageMap().getFoliageList()){
|
|
if(foliageType.getTokens().contains(FoliageType.TOKEN_AMBIENT)){
|
|
Globals.assetManager.addModelPathToQueue(foliageType.getModelPath());
|
|
Globals.assetManager.addShaderToQueue(vertexPath, fragmentPath);
|
|
}
|
|
}
|
|
ready = true;
|
|
}
|
|
|
|
/**
|
|
* Updates all grass entities
|
|
*/
|
|
public void update(){
|
|
if(ready){
|
|
//for each invalid cell, see if can be revalidated
|
|
for(String key : locationEvaluationCooldownMap.keySet()){
|
|
int cooldownTime = locationEvaluationCooldownMap.get(key);
|
|
cooldownTime--;
|
|
if(cooldownTime <= 0){
|
|
String split[] = key.split("_");
|
|
Vector3i worldPos = new Vector3i(Integer.parseInt(split[0]),Integer.parseInt(split[1]),Integer.parseInt(split[2]));
|
|
Vector3i voxelPos = new Vector3i(Integer.parseInt(split[3]),Integer.parseInt(split[4]),Integer.parseInt(split[5]));
|
|
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos);
|
|
//evaluate
|
|
if(
|
|
data.getWeight(voxelPos) > 0 &&
|
|
data.getWeight(new Vector3i(voxelPos.x,voxelPos.y + 1,voxelPos.z)) < 0 &&
|
|
typeSupportsFoliage(data.getType(voxelPos))
|
|
){
|
|
//create foliage cell
|
|
createFoliageCell(worldPos,voxelPos,0);
|
|
}
|
|
locationEvaluationCooldownMap.remove(key);
|
|
} else {
|
|
locationEvaluationCooldownMap.put(key, cooldownTime);
|
|
}
|
|
}
|
|
//invalidate foliage cells that have had their voxel changed
|
|
invalidateModifiedPositions();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a good position to put a new blade of grass
|
|
* @param centerPosition The player's position
|
|
* @return The new position for the blade of grass
|
|
*/
|
|
protected Vector3d getNewPosition(Vector3d centerPosition){
|
|
double angle = placementRandomizer.nextDouble() * Math.PI * 2;
|
|
double radius = placementRandomizer.nextDouble();
|
|
return new Vector3d(
|
|
centerPosition.x + Math.cos(angle) * radius,
|
|
centerPosition.y,
|
|
centerPosition.z + Math.sin(angle) * radius
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gets a new rotation for a blade of grass
|
|
* @return The rotation
|
|
*/
|
|
protected Quaterniond getNewRotation(){
|
|
return new Quaterniond().rotationX(-Math.PI / 2.0f).rotateLocalY(Math.PI * placementRandomizer.nextFloat()).normalize();
|
|
}
|
|
|
|
/**
|
|
* Gets a key for a foliage cell in the localCellMap
|
|
* @param worldPosition The world position of the cell
|
|
* @param voxelPosition The voxel position of the cell
|
|
* @return The key for the cell
|
|
*/
|
|
private String getFoliageCellKey(Vector3i worldPosition, Vector3i voxelPosition){
|
|
return worldPosition.x + "_" + worldPosition.y + "_" + worldPosition.z + "_" + voxelPosition.x + "_" + voxelPosition.y + "_" + voxelPosition.z;
|
|
}
|
|
|
|
|
|
/**
|
|
* Makes an already created entity a drawable, instanced entity (client only) by backing it with an InstancedActor
|
|
* @param entity The entity
|
|
* @param modelPath The model path for the model to back the instanced actor
|
|
* @param capacity The capacity of the instanced actor to draw
|
|
*/
|
|
public static void makeEntityTextureInstancedFoliage(Entity entity, String modelPath, int capacity){
|
|
entity.putData(EntityDataStrings.INSTANCED_ACTOR, Globals.clientInstanceManager.createInstancedActor(modelPath, vertexPath, fragmentPath, attributes, capacity));
|
|
entity.putData(EntityDataStrings.DATA_STRING_POSITION, new Vector3d(0,0,0));
|
|
entity.putData(EntityDataStrings.DATA_STRING_ROTATION, new Quaterniond().identity());
|
|
entity.putData(EntityDataStrings.DATA_STRING_SCALE, new Vector3f(1,1,1));
|
|
entity.putData(EntityDataStrings.DRAW_SOLID_PASS, true);
|
|
Globals.clientScene.registerEntity(entity);
|
|
Globals.clientScene.registerEntityToTag(entity, EntityTags.DRAW_INSTANCED_MANAGED);
|
|
}
|
|
|
|
/**
|
|
* Evaluates a chunk to see where foliage cells should be created or updated
|
|
* @param worldPos The world position of the chunk
|
|
*/
|
|
public void evaluateChunk(Vector3i worldPos){
|
|
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos);
|
|
for(int x = 0; x < ChunkData.CHUNK_SIZE; x++){
|
|
//can't go to very top 'cause otherwise there would be no room to put grass
|
|
for(int y = 0; y < ChunkData.CHUNK_SIZE - 1; y++){
|
|
for(int z = 0; z < ChunkData.CHUNK_SIZE; z++){
|
|
Vector3i currentPos = new Vector3i(x,y,z);
|
|
String key = getFoliageCellKey(worldPos, currentPos);
|
|
if(locationCellMap.get(key) != null){
|
|
//destroy if there's no longer ground or
|
|
//if the cell above is now occupied or
|
|
//if the lower cell is no longer supporting foliage
|
|
if(
|
|
data.getWeight(currentPos) <= 0 ||
|
|
data.getWeight(new Vector3i(x,y + 1,z)) > 0 ||
|
|
!typeSupportsFoliage(data.getType(currentPos))
|
|
){
|
|
//destroy
|
|
FoliageCell toDestroy = locationCellMap.get(key);
|
|
toDestroy.destroy();
|
|
activeCells.remove(toDestroy);
|
|
locationCellMap.remove(key);
|
|
} else {
|
|
//TODO: evaluate if foliage is placed well
|
|
}
|
|
} else {
|
|
//create if current is ground and above is air
|
|
if(
|
|
!locationEvaluationCooldownMap.containsKey(key) &&
|
|
data.getWeight(currentPos) > 0 &&
|
|
data.getWeight(new Vector3i(x,y + 1,z)) < 0 &&
|
|
typeSupportsFoliage(data.getType(currentPos)) &&
|
|
activeCells.size() < CELL_COUNT_MAX
|
|
){
|
|
//create foliage cell
|
|
createFoliageCell(worldPos,currentPos,1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//evaluate top cells if chunk above this one exists
|
|
ChunkData aboveData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(worldPos).add(0,1,0));
|
|
if(aboveData != null){
|
|
for(int x = 0; x < ChunkData.CHUNK_SIZE; x++){
|
|
for(int z = 0; z < ChunkData.CHUNK_SIZE; z++){
|
|
Vector3i currentPos = new Vector3i(x,ChunkData.CHUNK_SIZE-1,z);
|
|
String key = getFoliageCellKey(worldPos, currentPos);
|
|
if(locationCellMap.get(key) != null){
|
|
//destroy if there's no longer ground or
|
|
//if the cell above is now occupied or
|
|
//if the lower cell is no longer supporting foliage
|
|
if(
|
|
data.getWeight(currentPos) <= 0 ||
|
|
aboveData.getWeight(new Vector3i(x,0,z)) > 0 ||
|
|
!typeSupportsFoliage(data.getType(currentPos))
|
|
){
|
|
//destroy
|
|
FoliageCell toDestroy = locationCellMap.get(key);
|
|
toDestroy.destroy();
|
|
activeCells.remove(toDestroy);
|
|
locationCellMap.remove(key);
|
|
} else {
|
|
//TODO: evaluate if foliage is placed well
|
|
}
|
|
} else {
|
|
//create if current is ground and above is air
|
|
if(
|
|
data.getWeight(currentPos) > 0 &&
|
|
aboveData.getWeight(new Vector3i(x,0,z)) < 0 &&
|
|
typeSupportsFoliage(data.getType(currentPos)) &&
|
|
activeCells.size() < CELL_COUNT_MAX
|
|
){
|
|
//create foliage cell
|
|
createFoliageCell(worldPos,currentPos,1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//the length of the ray to ground test with
|
|
static final float RAY_LENGTH = 2.0f;
|
|
|
|
//the height above the chunk to start from when sampling downwards
|
|
static final float SAMPLE_START_HEIGHT = 1.0f;
|
|
|
|
/**
|
|
* Creates a foliage cell at a given position
|
|
* @param worldPos The world position
|
|
* @param voxelPos The voxel position
|
|
*/
|
|
private void createFoliageCell(Vector3i worldPos, Vector3i voxelPos, float initialGrowthLevel){
|
|
//get foliage types supported
|
|
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos);
|
|
List<String> foliageTypesSupported = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(data.getType(voxelPos)).getAmbientFoliage();
|
|
if(foliageTypesSupported != null){
|
|
Vector3d realPos = new Vector3d(
|
|
worldPos.x * ChunkData.CHUNK_SIZE + voxelPos.x,
|
|
worldPos.y * ChunkData.CHUNK_SIZE + voxelPos.y,
|
|
worldPos.z * ChunkData.CHUNK_SIZE + voxelPos.z
|
|
);
|
|
|
|
//get type
|
|
String foliageTypeName = foliageTypesSupported.get(placementRandomizer.nextInt() % foliageTypesSupported.size());
|
|
FoliageType foliageType = Globals.gameConfigCurrent.getFoliageMap().getFoliage(foliageTypeName);
|
|
|
|
//create cell and buffer
|
|
FoliageCell cell = new FoliageCell(worldPos, voxelPos, realPos);
|
|
ByteBuffer buffer = BufferUtils.createByteBuffer(TARGET_FOLIAGE_SPACING * TARGET_FOLIAGE_SPACING * SINGLE_FOLIAGE_DATA_SIZE_BYTES);
|
|
FloatBuffer floatBufferView = buffer.asFloatBuffer();
|
|
//construct simple grid to place foliage on
|
|
Vector3d sample_00 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(realPos).add(-0.5,SAMPLE_START_HEIGHT,-0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
|
Vector3d sample_01 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(realPos).add(-0.5,SAMPLE_START_HEIGHT, 0), new Vector3d(0,-1,0), RAY_LENGTH);
|
|
Vector3d sample_02 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(realPos).add(-0.5,SAMPLE_START_HEIGHT, 0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
|
Vector3d sample_10 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(realPos).add( 0,SAMPLE_START_HEIGHT,-0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
|
Vector3d sample_11 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(realPos).add( 0,SAMPLE_START_HEIGHT, 0), new Vector3d(0,-1,0), RAY_LENGTH);
|
|
Vector3d sample_12 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(realPos).add( 0,SAMPLE_START_HEIGHT, 0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
|
Vector3d sample_20 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(realPos).add( 0.5,SAMPLE_START_HEIGHT,-0.5), new Vector3d(0,-1,0), RAY_LENGTH);
|
|
Vector3d sample_21 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(realPos).add( 0.5,SAMPLE_START_HEIGHT, 0), new Vector3d(0,-1,0), RAY_LENGTH);
|
|
Vector3d sample_22 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(realPos).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
|
|
int drawCount = 0;
|
|
for(int x = 0; x < TARGET_FOLIAGE_SPACING; x++){
|
|
for(int z = 0; z < TARGET_FOLIAGE_SPACING; z++){
|
|
//get position to place
|
|
double rand1 = placementRandomizer.nextDouble();
|
|
double rand2 = placementRandomizer.nextDouble();
|
|
double relativePositionOnGridX = x / (1.0 * TARGET_FOLIAGE_SPACING) - 0.5 + rand1 / TARGET_FOLIAGE_SPACING;
|
|
double relativePositionOnGridZ = z / (1.0 * TARGET_FOLIAGE_SPACING) - 0.5 + rand2 / TARGET_FOLIAGE_SPACING;
|
|
double offsetX = relativePositionOnGridX;
|
|
double offsetZ = relativePositionOnGridZ;
|
|
//determine quadrant we're placing in
|
|
double offsetY = 0;
|
|
boolean addBlade = false;
|
|
if(relativePositionOnGridX >=0){
|
|
if(relativePositionOnGridZ >= 0){
|
|
relativePositionOnGridX += 0.5;
|
|
relativePositionOnGridZ += 0.5;
|
|
//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 += 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){
|
|
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();
|
|
floatBufferView.put((float)offsetX);
|
|
floatBufferView.put((float)offsetY);
|
|
floatBufferView.put((float)offsetZ);
|
|
floatBufferView.put((float)rotVar);
|
|
floatBufferView.put((float)rotVar2);
|
|
drawCount++;
|
|
}
|
|
}
|
|
}
|
|
buffer.position(0);
|
|
buffer.limit(TARGET_FOLIAGE_SPACING * TARGET_FOLIAGE_SPACING * SINGLE_FOLIAGE_DATA_SIZE_BYTES);
|
|
//construct data texture
|
|
Texture dataTexture = new Texture(buffer,SINGLE_FOLIAGE_DATA_SIZE_BYTES / 4,TARGET_FOLIAGE_SPACING * TARGET_FOLIAGE_SPACING);
|
|
|
|
//create entity
|
|
Entity grassEntity = EntityCreationUtils.createClientSpatialEntity();
|
|
|
|
TextureInstancedActor.attachTextureInstancedActor(grassEntity, foliageType.getModelPath(), vertexPath, fragmentPath, dataTexture, drawCount);
|
|
EntityUtils.getPosition(grassEntity).set(realPos);
|
|
EntityUtils.getRotation(grassEntity).set(0,0,0,1);
|
|
EntityUtils.getScale(grassEntity).set(new Vector3d(4.0, 4.0, 4.0));
|
|
//add ambient foliage behavior tree
|
|
AmbientFoliage.attachAmbientFoliageTree(grassEntity, initialGrowthLevel, foliageType.getGrowthModel().getGrowthRate());
|
|
cell.addEntity(grassEntity);
|
|
|
|
|
|
activeCells.add(cell);
|
|
locationCellMap.put(getFoliageCellKey(worldPos, voxelPos),cell);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets whether the voxel type supports foliage or not
|
|
* @param type
|
|
* @return
|
|
*/
|
|
private boolean typeSupportsFoliage(int type){
|
|
if(Globals.gameConfigCurrent.getVoxelData().getTypeFromId(type) != null){
|
|
return Globals.gameConfigCurrent.getVoxelData().getTypeFromId(type).getAmbientFoliage() != null;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Invalidates a foliage cell at a position, destroying all foliage in the cell and unregistering it.
|
|
* Furthermore, it adds it to a cooldown queue to wait until it can recreate foliage
|
|
* @param worldPosition The world position of the cell
|
|
* @param voxelPosition The voxel position of the cell
|
|
*/
|
|
private void invalidateCell(Vector3i worldPosition, Vector3i voxelPosition){
|
|
String key = getFoliageCellKey(worldPosition, voxelPosition);
|
|
if(!locationEvaluationCooldownMap.containsKey(key)){
|
|
locationEvaluationCooldownMap.put(key,EVALUATION_COOLDOWN);
|
|
FoliageCell cell = locationCellMap.get(key);
|
|
if(cell != null){
|
|
//destroy
|
|
FoliageCell toDestroy = locationCellMap.get(key);
|
|
toDestroy.destroy();
|
|
activeCells.remove(toDestroy);
|
|
locationCellMap.remove(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalidates the foliage cells for all modified chunks
|
|
*/
|
|
private void invalidateModifiedPositions(){
|
|
for(ChunkData chunk : Globals.clientTerrainManager.getAllChunks()){
|
|
if(chunk.getModifiedPositions().size() > 0){
|
|
for(Vector3i position : chunk.getModifiedPositions()){
|
|
Globals.clientFoliageManager.invalidateCell(Globals.clientTerrainManager.getPositionOfChunk(chunk), position);
|
|
}
|
|
chunk.resetModifiedPositions();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws all foliage in the foliage manager
|
|
*/
|
|
public void draw(){
|
|
for(FoliageCell cell : activeCells){
|
|
cell.draw(modelMatrixAttribute);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|