Grass, as far as possible ( in progress )

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;
   }

VegetationBank.java

import java.util.HashMap;

import com.jme.image.Texture;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.DistanceSwitchModel;
import com.jme.scene.SharedMesh;
import com.jme.scene.SharedNode;
import com.jme.scene.Node;
import com.jme.scene.TriMesh;
import com.jme.scene.VBOInfo;
import com.jme.scene.Spatial.LightCombineMode;
import com.jme.scene.lod.DiscreteLodNode;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Quad;
import com.jme.scene.shape.Torus;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.RenderState.StateType;
import com.jme.util.TextureManager;
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;

public class VegetationBank {
   private static HashMap<Short, Node> vegetationNodeBank =
      new HashMap<Short, Node>();
   private static HashMap<Short, TriMesh[]> vegetationMeshBank =
      new HashMap<Short, TriMesh[]>();
      
   private static BatchingVegetation batchTool = new BatchingVegetation();
   public static SharedNode getNewSharedVegetation(short index){
      if( !vegetationNodeBank.containsKey(index) ){
         vegetationNodeBank.put(index, createVegetationNode(index));
      }
      return new SharedNode(vegetationNodeBank.get(index));
   }
   private static Node createVegetationNode(short index){

      if( !vegetationMeshBank.containsKey(index) ){
            vegetationMeshBank.put(index, new TriMesh[]{
                  batchTool.createVegetationMesh(2, 3, VegetationManager.VEGETATION_PACK_SIZE )});
            
      }
      //TODO: LOD
      Node n = new Node();
      n.attachChild(vegetationMeshBank.get(index)[0]);
      vegetationNodeBank.put(index, n);

      VegetationManager.applyVegetationTexture((short)0, n);
          n.setLightCombineMode(LightCombineMode.Off);
      return n;
   }
   
}

BatchingVegetation.java

import java.util.HashMap;
import java.util.Random;

import com.jme.bounding.BoundingBox;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Spatial;
import com.jme.scene.TexCoords;
import com.jme.scene.TriMesh;
import com.jme.scene.VBOInfo;
import com.jme.scene.Spatial.NormalsMode;
import com.jme.scene.geometryinstancing.GeometryBatchInstance;
import com.jme.scene.geometryinstancing.GeometryBatchInstanceAttributes;
import com.jme.scene.geometryinstancing.instance.GeometryBatchCreator;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.LightState;
import com.jme.scene.state.RenderState.StateType;
import com.jme.system.DisplaySystem;
import com.jme.util.geom.BufferUtils;

public class BatchingVegetation {
   
   private static ColorRGBA INSTANCE_COLOR = new ColorRGBA(0.3f,0.4f,0.2f,1);

   private static VBOInfo DEFAULT_VBO_INFO = new VBOInfo(true);

   private GeometryBatchCreator creator;

   private Vector3f instanceTranslation = new Vector3f();
   private TriMesh mesh;
   private int verticeSize = 0;

   public BatchingVegetation() {
   }
   public GeometryBatchCreator getCreator(){
      return creator;
   }

   public TriMesh createVegetationMesh(float scale, int turnNumber, short size) {
      Quad q = new Quad("triMeshVegQuad",1,1);
      q.setTextureCoords(VegetationManager.VEGETATION_QUAD_TEXCOORDS);
      
      creator = new GeometryBatchCreator();
      
      verticeSize = turnNumber * 4 * size;
      Random rand = new Random();
      float orand = 0;
      float frand = 0;
      for(int x = 0; x < size; x++)
      {
         for(int z = 0; z < size; z++)
         {
            orand = rand.nextFloat() - .5f;
            for(int i = 0; i < turnNumber; i++)
            {
               frand = (rand.nextFloat()*0.50f) + 0.50f;
               GeometryBatchInstance instance =
                  new GeometryBatchInstance(q,
                        new GeometryBatchInstanceAttributes(
                              instanceTranslation.set((x*VegetationManager.VEGETATION_STEP)+orand, scale/2, (z*VegetationManager.VEGETATION_STEP)+orand),
                              new Vector3f(scale*frand, scale*frand, scale*frand),
                              new Quaternion().fromAngleAxis((float)((Math.PI*2)/turnNumber)*i, Vector3f.UNIT_Y),
                              new ColorRGBA(INSTANCE_COLOR.r+(rand.nextFloat()/10f),
                                    INSTANCE_COLOR.g+(rand.nextFloat()/8f),
                                    INSTANCE_COLOR.b+(rand.nextFloat()/8f),
                                    INSTANCE_COLOR.a
                                    )
                              ));
      
               creator.addInstance(instance);
            }
         }
      }
      batch();
      return mesh;      
   }
   
   public void clean(){
      creator.clearInstances();
   }
   
   public void batch(){
      // Create a TriMesh
      mesh = new TriMesh();
        mesh.setModelBound(new BoundingBox());

        // Create the batch's buffers
        mesh.setIndexBuffer(BufferUtils.createIntBuffer(
                creator.getNumIndices()));
        mesh.setVertexBuffer(BufferUtils.createVector3Buffer(
              creator.getNumVertices()));
        mesh.setNormalBuffer(BufferUtils.createVector3Buffer(
              creator.getNumVertices()));
        for (int normalIndex = 0; normalIndex < verticeSize; normalIndex++) {
              BufferUtils.setInBuffer(Vector3f.UNIT_Y, mesh.getNormalBuffer(),
                  normalIndex);
      }
       
        mesh.setTextureCoords(new TexCoords(BufferUtils.createVector2Buffer(
              creator.getNumVertices())), 0);
        mesh.setColorBuffer(BufferUtils.createFloatBuffer(
              creator.getNumVertices() * 4));
       
        mesh.updateModelBound();
      
        // Commit the instances to the mesh batch
        creator.commit(mesh);
       
        mesh.setVBOInfo(DEFAULT_VBO_INFO);
        mesh.updateModelBound();
        //mesh.lockMeshes();
        clean();
   }

}