Post before: http://www.jmonkeyengine.com/jmeforum/index.php?topic=10986.0
Grass & Vegetation system
The goal is to display vegetation in streaming on infinite terrain.
In a first time, VegetationManager can display many grass (over 256 meter line of view) as sharedNode, construct mesh with BatchGeometry…
Only grass for now, without update in order to erase grass so far and build new grass needed.
Anybody can try to make system faster, stronger and implante new features.
This is my method, peharps is not the better way to do massive vegetation…
TODO list:
- Update line of view (erase old grass, make new to horizon)
- Algorithm to reduce density with distance
- LOD
- Class optimization for faster rendering
code to use VegetationManager:
VegetationManager manager = new VegetationManager(rootNode, Main.getToriaCore().getCamera());
rootNode.attachChild(manager);
short[] map = new short[1024];
manager.updateMap(map, new Vector3f());
VegetationManager
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import java.util.Vector;
import com.jme.image.Texture;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.Renderer;
import com.jme.scene.BillboardNode;
import com.jme.scene.Node;
import com.jme.scene.SharedMesh;
import com.jme.scene.SharedNode;
import com.jme.scene.Spatial;
import com.jme.scene.TexCoords;
import com.jme.scene.TriMesh;
import com.jme.scene.VBOInfo;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.BlendState;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;
import com.jme.util.geom.BufferUtils;
import com.toria.client.main.Main;
import com.toria.client.main.ToriaCore;
import com.toria.client.map.msp.VegetationKey;
import com.toria.common.beans.VegetationDefinitions;
/**
* @author Nexam
*
*/
public class VegetationManager extends Node{
/**
* - C O N F I G -
*/
public static int VEGETATION_DISTANCE = 500;
public static float VEGETATION_DENSITY_MAX = 1F;
public static boolean VEGETATION_RENDER = true;
public static short MAP_HORIZONTALE_SCALE = 4;
public static float UPDATE_DELAY = 0.2F; // In seconds
public static int VEGETATION_AVERAGE_COUNTER = 0;
public static short VEGETATION_PACK_SIZE = 8;
public static BlendState VEGETATION_BLENDSTATE;
public static TexCoords VEGETATION_QUAD_TEXCOORDS;
public static float VEGETATION_STEP = 1.5f;
/**
* - V A R I A B L E S -
*/
private Node rootNode = null;
private Camera camera = null;
private short[] vegetationMap = new short[0];
private float timeCounter = 0;
private VBOInfo vbo = null;
private Vector3f tmpVec = new Vector3f();
public VegetationManager(Node rootNode, Camera camera){
this.rootNode = rootNode;
this.camera = camera;
vbo = new VBOInfo(true);
initializeBank();
}
/**
* - F U N C T I O N S -
*/
private void initializeBank(){
VEGETATION_BLENDSTATE = Main.getToriaCore().getDisplay().getRenderer().createBlendState();
VEGETATION_BLENDSTATE.setSourceFunctionAlpha( BlendState.SourceFunction.SourceAlpha );
VEGETATION_BLENDSTATE.setDestinationFunctionAlpha( BlendState.DestinationFunction.DestinationAlpha );
VEGETATION_BLENDSTATE.setEnabled( true );
VEGETATION_BLENDSTATE.setTestEnabled( true );
VEGETATION_BLENDSTATE.setBlendEnabled( true );
VEGETATION_BLENDSTATE.setReference( 0.6f );
VEGETATION_BLENDSTATE.setTestFunction( BlendState.TestFunction.GreaterThanOrEqualTo );
FloatBuffer buff = BufferUtils.createVector2Buffer(4);
buff.put(0).put(1);
buff.put(0).put(0);
buff.put(1).put(0);
buff.put(1).put(1);
VEGETATION_QUAD_TEXCOORDS = new TexCoords(buff);
}
private boolean canUpdate(float tpf){
timeCounter += tpf;
if( timeCounter < UPDATE_DELAY )
return false;
else if(vegetationMap.length <= 1)
return false;
timeCounter = 0;
return true;
}
public void update(float tpf)
{
if( canUpdate(tpf)){
}
}
public void updateMap(short[] vegetationMap, Vector3f offset){
this.vegetationMap = vegetationMap;
this.unlockTransforms();
this.localTranslation = offset;
if( VEGETATION_RENDER )
processMap();
this.lockTransforms();
}
private void processMap(){
SharedNode s = null;
Random rand = new Random();
int id = 0;
for(float x = 0; x < Math.sqrt(this.vegetationMap.length); x++)
{
for(float z = 0; z < Math.sqrt(this.vegetationMap.length); z++)
{
if( id >= this.vegetationMap.length )
break;
s = VegetationBank.getNewSharedVegetation(this.vegetationMap[id]);
s.setLocalTranslation((x*VEGETATION_STEP)*VEGETATION_PACK_SIZE, 55, (z*VEGETATION_STEP)*VEGETATION_PACK_SIZE);
s.getLocalTranslation().y = getYat(s.getLocalTranslation().x, s.getLocalTranslation().z);
VEGETATION_AVERAGE_COUNTER++;
this.attachChild(s);
s.lockMeshes();
id++;
}
}
this.lockMeshes();
updateRenderState();
}
@Override
public void onDraw(Renderer r) {
if ( children == null || !VEGETATION_RENDER ) {
return;
}
super.onDraw(r);
Spatial child;
for ( int i = 0, cSize = children.size(); i < cSize; i++ ) {
child = children.get( i );
if ( child != null ) {
float distSquared = tmpVec.set( camera.getLocation() ).distance(child.getLocalTranslation());
if ( distSquared <= VEGETATION_DISTANCE ) {
child.onDraw( r );
}
}
}
}
private static TextureState TEXSTATE = null;
public static void applyVegetationTexture(short index, Spatial n){
if( TEXSTATE == null ){
String texturePath = VegetationDefinitions.getInstance().getVegetationBean(index);
TEXSTATE = Main.getToriaCore().getDisplay().getRenderer().createTextureState();
TEXSTATE.setEnabled(true);
Texture t1 = TextureManager.loadTexture(
ToriaCore.class.getClassLoader().getResource(
texturePath),
Texture.MinificationFilter.Trilinear,
Texture.MagnificationFilter.Bilinear);
TEXSTATE.setTexture(t1,0);
}
n.setRenderState(TEXSTATE);
n.setRenderState(VegetationManager.VEGETATION_BLENDSTATE);
}
private static Vector3f tmpYvector = new Vector3f();
public static float getYat(float x, float z){
/**
* Implant your own height checker here
*/
if( Main.getToriaCore().getSceneNode().getMapManager() != null )
{
tmpYvector.set(x,0,z);
return Main.getToriaCore().getSceneNode().getMapManager().getHeightFor(tmpYvector);
}
return 50;
}