package electrosphere.client.foliagemanager; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Random; import java.util.Set; import org.joml.Matrix4f; import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; 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.types.camera.CameraEntityUtils; import electrosphere.renderer.actor.instance.InstancedActor; import electrosphere.renderer.buffer.ShaderAttribute; import electrosphere.renderer.buffer.HomogenousUniformBuffer.HomogenousBufferTypes; /** * Manages foliage (grass, small plants, etc) that should be shown, typically instanced */ public class ClientFoliageManager { //threshold for a grass blade to relocate to a new position static final float GRASS_RELOCATION_THRESHOLD = 5f; //amount of grass entities to manage static final int grassCapacity = 10000; //Random for finding new positions for foliage Random placementRandomizer = new Random(); //The list of grass entities currently in use Set grassEntities = new HashSet(); //Used to prevent concurrent usage of grassEntities set boolean ready = false; //The map of all attributes for instanced foliage static final Map attributes = new HashMap(); //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); } //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 grass model Globals.assetManager.addModelPathToQueue("models/grass1.fbx"); Vector3d centerPosition = new Vector3d(0,0,0); //create grass entities Entity grassEntity = EntityCreationUtils.createClientSpatialEntity(); makeEntityInstancedFoliage(grassEntity, "models/grass1.fbx", grassCapacity); EntityUtils.getPosition(grassEntity).set(getNewPosition(centerPosition)); EntityUtils.getRotation(grassEntity).set(getNewRotation()); EntityUtils.getScale(grassEntity).set(new Vector3d(2.0, 2.0, 2.0)); grassEntities.add(grassEntity); for(int i = 0; i < grassCapacity - 1; i++){ grassEntity = EntityCreationUtils.createClientSpatialEntity(); makeEntityInstancedFoliage(grassEntity, "models/grass1.fbx", grassCapacity); EntityUtils.getPosition(grassEntity).set(getNewPosition(centerPosition)); EntityUtils.getRotation(grassEntity).set(getNewRotation()); EntityUtils.getScale(grassEntity).set(new Vector3d(2.0, 2.0, 2.0)); grassEntities.add(grassEntity); } ready = true; } /** * Updates all grass entities */ public void update(){ if(ready){ Matrix4f modelMatrix = new Matrix4f(); Vector3f cameraCenter = CameraEntityUtils.getCameraCenter(Globals.playerCamera); for(Entity grassEntity : grassEntities){ Vector3d grassPosition = EntityUtils.getPosition(grassEntity); Quaternionf grassRotation = EntityUtils.getRotation(grassEntity); Vector3d playerPosition = EntityUtils.getPosition(Globals.playerEntity); InstancedActor instancedActor = InstancedActor.getInstancedActor(grassEntity); //update position if(playerPosition.distance(grassPosition) > GRASS_RELOCATION_THRESHOLD){ grassPosition.set(getNewPosition(playerPosition)); grassRotation.set(getNewRotation()); } //update uniforms // instancedActor.setUniform("position", grassPosition); // instancedActor.setUniform("rotation", new Vector4d( // grassRotation.x, // grassRotation.y, // grassRotation.z, // grassRotation.w)); // instancedActor.setUniform("scale", new Vector3d(EntityUtils.getScale(grassEntity))); modelMatrix = modelMatrix.identity(); Vector3f cameraModifiedPosition = new Vector3f((float)grassPosition.x,(float)grassPosition.y,(float)grassPosition.z).sub(cameraCenter); modelMatrix.translate(cameraModifiedPosition); modelMatrix.rotate(grassRotation); modelMatrix.scale(EntityUtils.getScale(grassEntity)); instancedActor.setAttribute(modelMatrixAttribute, modelMatrix); //draw instancedActor.draw(Globals.renderingEngine.getRenderPipelineState()); } } } /** * 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() * GRASS_RELOCATION_THRESHOLD; 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 Quaternionf getNewRotation(){ return new Quaternionf().rotationX(-(float)Math.PI / 2.0f).rotateLocalY((float)Math.PI * placementRandomizer.nextFloat()); } /** * 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 */ public static void makeEntityInstancedFoliage(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 Quaternionf().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); } }