jME Terra development thread

In several threads and some pms people have expressed an intrest to do some work on my terrain engine. Also some people coded up some things for it in the past, so they might be intrested in it as well.



Read about jTerra:



Source release: http://www.jmonkeyengine.com/jmeforum/index.php?topic=3098

Demo release (old): http://www.jmonkeyengine.com/jmeforum/index.php?topic=2979

"website": jME Terra

Google code project: http://code.google.com/p/jmeterra/



You can use this thread to let people know what you're working on or ask questions about development (for questions about it's usage please keep using the source release thread). I'm slowly getting more involved in jME development again, so I'll make an effort to merge in changes (if any) and release source bundles, and do some new additions of my own. If there's enough intrest we might move to something like SVN in the future of course.

There's now a google code project and issue list:



http://code.google.com/p/jmeterra/issues/list



I'll keep this little list below till everyone is moved over.





TODO:


  • scaling a TerraView other than terrain height.
  • adding a TerraView anywhere other than at (0,0,0).
  • terrain following / getting the height of terrain at a point.
  • logging
  • documentation.
  • more examples
  • remove occasional stuttering during loading. llama
  • modifying terrain (basic) prism / hevee

Aha - i have duplicated things - but let this now be known as the correct home for terrain revamp thread. In particular carrying on from the excellent work Llama has done





There are tasks as Llama has specified, if you have done any work on Llmama's terrain or intend to, best to say.



a list of what is needed can be found here

http://www.jmonkeyengine.com/jmeforum/index.php?topic=3098.0



Also another task is :-

Lllama has expressed that it would be nice to integrate in using shaders to avoid "popping" when a block changes to another LOD level.



Also to continue on the seperation of the terrain geometry and rendering.


Moreover, a good technical (and possibly brief) documentation could be an help for all developers who want to contribute. There is any? If not, those who already tested jME Terra could contribute to documentation effort.

I updated the second post with a TODO list, and put my name at one of the tasks.



Aside from a TODO, if people want to make new features I'll also put it up there.

Will have some time end of this week.



ill take the task

terrain following / getting the height of terrain at a point.



and add documentation as i go…

Ehi, llama, could you tell me what kind of algorithm did you use for terrain DLOD?

I dunno what the algorithm is called, I just build it… but apperently it's very obvious since many other projects use the same techniques.



It's terrain consisting out of blocks, that switches to a different detail level by using an alternative indexbuffer, based on how far from the camera you are. There's special index buffers to fix the "seams" you get between 2 different blocks when they use different detail levels.

Started by building up a click and raise / lower terrain test, however could not complete it just yet, finding it hard to understand the code.



Ill post up to where I am, and do round 2 later


package org.llama.test;

import jmetest.input.TestAbsoluteMouse;

import org.llama.jmex.terra.TerraData;
import org.llama.jmex.terra.TerraManager;
import org.llama.jmex.terra.TerraMesh;
import org.llama.jmex.terra.TerraView;
import org.llama.jmex.terra.XYKey;
import org.llama.jmex.terra.format.JMEXMapStore;
import org.llama.jmex.terra.util.SimpleTerraView;

import com.jme.app.AbstractGame;
import com.jme.image.Texture;
import com.jme.input.AbsoluteMouse;
import com.jme.input.KeyboardLookHandler;
import com.jme.input.MouseInput;
import com.jme.intersection.PickResults;
import com.jme.intersection.TrianglePickResults;
import com.jme.math.Ray;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jmex.terrain.util.AbstractHeightMap;
import com.jmex.terrain.util.HillHeightMap;

public class RaiseLowerHeightMapTest extends AbstractTerraTest {
      
      TerraManager tm;
      int blocksize = 64;
      int mapsize = 128*20;
      
      float hillsize = 75.0f;
      int loading = 900;
      int rendering = 700;
      int iterations = 200000;

      public int mapmulti = 3;
      
      public TerraManager getManager() {

         TerraManager tm = new TerraManager("My Terrain", blocksize * 3, blocksize);

         AbstractHeightMap ahm;
         AbstractHeightMap.NORMALIZE_RANGE = 10000f;

         ahm = new HillHeightMap(mapsize, iterations, 10.0f, hillsize, (byte) 3);
//         ahm = new MidPointHeightMap(32*32, 1f);
//         ahm = new FaultFractalHeightMap(32*32, 32*32, 0, 255, 0.75f);
         flatten(ahm);
         tm.setGlobalMapStore(new JMEXMapStore(ahm, blocksize * mapmulti));
         return tm;
      }
      
      
      private void flatten(AbstractHeightMap ahm) {
         int [] heights  = ahm.getHeightMap();
         for(int i = 0; i < heights.length; i++) {
            heights[i] = (int)hillsize / 2;
         }
      }

      public TerraView getView() {
         tm = getManager();
         tv = new SimpleTerraView("my world", tm, taskManager, cam, getProperties(0.01f, 1), false, loading, -1, rendering, lod, false);
         
         return tv;

      }

      public void doInit() {
         input = new KeyboardLookHandler(cam, 20, 1);
         createCustomCursor();
      }

      
      public void doUpdate() {
         if(MouseInput.get().isButtonDown(0)) {
            detectClick(true);
         } else if(MouseInput.get().isButtonDown(1)) {
            detectClick(false);
         }
      }

      public void detectClick(boolean up) {
         Vector2f screenPos = new Vector2f(MouseInput.get().getXAbsolute(), MouseInput.get().getYAbsolute());
         Vector3f startPoint = DisplaySystem.getDisplaySystem().getWorldCoordinates(screenPos, 0);
         // Get the world location of that X,Y value - far
         Vector3f endPoint = DisplaySystem.getDisplaySystem().getWorldCoordinates(screenPos, 1);
         Ray ray = new Ray(startPoint, endPoint.subtract(startPoint));

         results.clear();
         rootNode.findPick(ray, results);
         
         Vector3f loc = new Vector3f();
         Vector3f[] vertex = new Vector3f[3];
         boolean foundMeshHit = false;
         for(int pickIndex = 0; pickIndex < results.getNumber(); pickIndex++) {
            TriMesh mesh = (TriMesh) results.getPickData(pickIndex).getTargetMesh().getParentGeom();
            
            if(mesh instanceof TerraMesh) {
               
               for (int j = 0; j < mesh.getTriangleCount(); j++) {
                  mesh.getTriangle(j, vertex);
                      // points[j] = vertex[j];                    
                       foundMeshHit = (ray.intersectWhere(vertex[0].addLocal(mesh.getWorldTranslation()),
                           vertex[1].addLocal(mesh.getWorldTranslation()),
                           vertex[2].addLocal(mesh.getWorldTranslation()), loc));
                       if(foundMeshHit){        
                          TerraMesh m = (TerraMesh)mesh;
                          System.out.println(m);
                          int x = (int)(vertex[0].x);
                          int z = (int)(vertex[0].z);
                          System.out.println("x = " + x);
                          System.out.println("z = " + z);
                          
                          
                          XYKey key = m.xy;
                          TerraData terraData = tm.getTerraData(key.x, key.y);
                          
                          Object a = tm.get(key);
                          
                          System.out.println(a);
                          //terraData.indicesbuffer.get();
                          
                          
                          tm.getMapAsByteBuffer(key.x, key.y);
                          
                       }
               }
            }
         }
      }
      
      
      public static void main(String[] args) {
         RaiseLowerHeightMapTest app = new RaiseLowerHeightMapTest();
         app.setArgs(args);
         app.setDialogBehaviour(AbstractGame.NEVER_SHOW_PROPS_DIALOG);
         app.start();
         
      }

      public void parseArgs(String[] args) {
         switch (args.length) {
         case 5:
         case 7:
            System.out.println("Specify all parameters of this group!");
            System.exit(0);
            break;

         default:
            if (arg(8, -1) == -1) {
               lod = null;
            } else {
               lod = new int[args.length - 8];
               for (int k = 8; k < args.length; k++) {
                  lod[k - 8] = arg(k, 0);
               }
            }
         case 8:
            rendering = arg(6, rendering);
            loading = arg(7, loading);
         case 6:
            fogstart = arg(4, (int) fogstart);
            fogend = arg(5, (int) fogend);
            
         case 4:
            blocksize = arg(3, blocksize);
         case 3:
            hillsize = arg(2, (int) hillsize);
         case 2:
            iterations = arg(1, iterations);
         case 1:
            mapsize = arg(0, mapsize);
         case 0:
            System.out.println("Please wait...");
         }
         
      }
      
      protected void createCustomCursor() {
         AbsoluteMouse mouse = new AbsoluteMouse( "cursor", display.getWidth(), display.getHeight() );         
         

          TextureState cursor = display.getRenderer().createTextureState();
              cursor.setEnabled(true);
              cursor.setTexture(
                       TextureManager.loadTexture(
                             TestAbsoluteMouse.class.getClassLoader().getResource("jmetest/data/cursor/test.PNG"),
                           Texture.MM_LINEAR, Texture.FM_LINEAR)
                     );
              //mouse.setRenderState(cursor);
              mouse.registerWithInputHandler( input );
              mouse.setZOrder(0);
              mouse.setCullMode( Spatial.CULL_NEVER );
              MouseInput.get().setCursorVisible( true );
              rootNode.attachChild(mouse);
       }
      
      PickResults results = new TrianglePickResults() {
         
         public void processPick() {      
         }
      };
      
   }



Need to add the following to AbstractTerraTest

protected void simpleInitGame() {
      
      taskManager = new TaskManager(timer);

      DirectionalLight dl = new DirectionalLight();
      dl.setEnabled(true);
      dl.setDirection(new Vector3f(.1f, -1, .1f));
      dl.setAmbient(new ColorRGBA(.5f, .5f, .5f, .9f));
      lightState.detachAll();
      lightState.attach(dl);

      fogstate = display.getRenderer().createFogState();
      fogstate.setDensity(0.5f);
      fogstate.setColor(ColorRGBA.black);
      fogstate.setEnd(fogend);
      fogstate.setStart(fogstart);
      fogstate.setDensityFunction(FogState.DF_LINEAR);
      fogstate.setApplyFunction(FogState.AF_PER_VERTEX);
      rootNode.setRenderState(fogstate);
      
      Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 1);

      display.setTitle("Press F to toggle Fog");

      KeyBindingManager.getKeyBindingManager().set(CMD_FOG, KeyInput.KEY_F);

   
      cam.setFrustumPerspective(45f, 640f / 480f, 1f, fogend);
      cam.setLocation(new Vector3f(0, 200.5f, 0));
      cam.update();
      cam.lookAt(new Vector3f(0, 0, 0), new Vector3f(1f, 0.5f, 1f));
      cam.update();
      
      cullstate = display.getRenderer().createCullState();
      cullstate.setCullMode(CullState.CS_BACK);
      
      tv = getView();
      
      tv.updateNextFrame();
      tv.setRenderState(cullstate);
      tv.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
      rootNode.attachChild(tv);

      if (tex) {
         Texture tex = TextureManager.loadTexture(getUrl("128.png"), Texture.MM_NEAREST, Texture.FM_NEAREST);
         tex.setWrap(Texture.WM_WRAP_S_WRAP_T);
         TextureState ts = display.getRenderer().createTextureState();
         ts.setTexture(tex);
         tv.setRenderState(ts);
      }
      doInit();
   }
   
   protected void simpleUpdate() {
      tv.update();
      taskManager.runTasks(0.02f);

      doUpdate();
      
      if (KeyBindingManager.getKeyBindingManager().isValidCommand(CMD_FOG, false)) {
         fogstate.setEnabled(!fogstate.isEnabled());
         if (fogstate.isEnabled())
            cam.setFrustumFar(fogend);
         else
            cam.setFrustumFar(5000);
         cam.update();
      }
   }
   
   public abstract void doInit();
   
   public abstract void doUpdate();



Ok - all done - setting the heightmap is now do - able.



Ill post up a screenie first ( yep - this is my attempt at drawing a Llama )



Test Class


package org.llama.test;

import jmetest.input.TestAbsoluteMouse;

import org.llama.jmex.terra.TerraData;
import org.llama.jmex.terra.TerraManager;
import org.llama.jmex.terra.TerraMesh;
import org.llama.jmex.terra.TerraView;
import org.llama.jmex.terra.XYKey;
import org.llama.jmex.terra.format.JMEXMapStore;
import org.llama.jmex.terra.util.SimpleTerraView;

import com.jme.app.AbstractGame;
import com.jme.image.Texture;
import com.jme.input.AbsoluteMouse;
import com.jme.input.KeyboardLookHandler;
import com.jme.input.MouseInput;
import com.jme.intersection.PickResults;
import com.jme.intersection.TrianglePickResults;
import com.jme.math.Ray;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jmex.terrain.util.AbstractHeightMap;
import com.jmex.terrain.util.HillHeightMap;

public class RaiseLowerHeightMapTest extends AbstractTerraTest {
   
   TerraManager tm;
   int blocksize = 64;
   int mapsize = 128*20;
   float hillsize = 75.0f;
   int loading = 900;
   int rendering = 700;
   int iterations = 200000;
   public int mapmulti = 3;
   
   public TerraManager getManager() {
      TerraManager tm = new TerraManager("My Terrain", blocksize * 3, blocksize);
      AbstractHeightMap ahm;
      AbstractHeightMap.NORMALIZE_RANGE = 10000f;
      ahm = new HillHeightMap(mapsize, iterations, 10.0f, hillsize, (byte) 3);
      flatten(ahm);
      tm.setGlobalMapStore(new JMEXMapStore(ahm, blocksize * mapmulti));
      return tm;
   }
   
   private void flatten(AbstractHeightMap ahm) {
      int [] heights  = ahm.getHeightMap();
      for(int i = 0; i < heights.length; i++) {
         heights[i] = 87;
      }
   }

   public TerraView getView() {
      tm = getManager();
      tv = new SimpleTerraView("my world", tm, taskManager, cam, getProperties(0.01f, 1), false, loading, -1, rendering, lod, false);
      return tv;
   }

   public void doInit() {
      input = new KeyboardLookHandler(cam, 20, 1);
      createCustomCursor();
   }
   
   public void doUpdate() {
      if(MouseInput.get().isButtonDown(0)) {
         detectClick(true);
      } else if(MouseInput.get().isButtonDown(1)) {
         detectClick(false);
      }
   }

   public void detectClick(boolean up) {
      Vector2f screenPos = new Vector2f(MouseInput.get().getXAbsolute(), MouseInput.get().getYAbsolute());
      Vector3f startPoint = DisplaySystem.getDisplaySystem().getWorldCoordinates(screenPos, 0);
      // Get the world location of that X,Y value - far
      Vector3f endPoint = DisplaySystem.getDisplaySystem().getWorldCoordinates(screenPos, 1);
      Ray ray = new Ray(startPoint, endPoint.subtract(startPoint));

      results.clear();
      rootNode.findPick(ray, results);
      
      Vector3f loc = new Vector3f();
      Vector3f[] vertex = new Vector3f[3];
      boolean foundMeshHit = false;
      for(int pickIndex = 0; pickIndex < results.getNumber(); pickIndex++) {
         TriMesh mesh = (TriMesh) results.getPickData(pickIndex).getTargetMesh().getParentGeom();
         
         if(mesh instanceof TerraMesh) {
            
            for (int j = 0; j < mesh.getTriangleCount(); j++) {
               mesh.getTriangle(j, vertex);
                                    
                    foundMeshHit = (ray.intersectWhere(vertex[0].addLocal(mesh.getWorldTranslation()),
                        vertex[1].addLocal(mesh.getWorldTranslation()),
                        vertex[2].addLocal(mesh.getWorldTranslation()), loc));
                    if(foundMeshHit){        
                       TerraMesh terraMesh = (TerraMesh)mesh;
                       System.out.println(terraMesh);
                       int x = (int)(vertex[0].x);
                       int z = (int)(vertex[0].z);
                       
                       XYKey key = terraMesh.xy;
                       TerraData terraData = tm.getTerraData(key.x, key.y);
                       
                       int point = tm.getPoint(x, z);
                       
                       tm.setPoint(2000,x, z);
                       boolean hasLockedBounds = terraMesh.hasLockedBounds();
                       if(hasLockedBounds) {
                          terraMesh.unlockBounds();
                       }
                       
                       terraData.updateTerra();
                       terraData.updateMesh();
                       terraData.getTerraMesh().buildTerra(terraData);
                       
                       if(hasLockedBounds) {
                          terraMesh.lockBounds();
                       }
                    }
            }
         }
      }
   }
   
   
   public static void main(String[] args) {
      RaiseLowerHeightMapTest app = new RaiseLowerHeightMapTest();
      app.setArgs(args);
      app.setDialogBehaviour(AbstractGame.NEVER_SHOW_PROPS_DIALOG);
      app.start();
      
   }

   public void parseArgs(String[] args) {
      switch (args.length) {
      case 5:
      case 7:
         System.out.println("Specify all parameters of this group!");
         System.exit(0);
         break;

      default:
         if (arg(8, -1) == -1) {
            lod = null;
         } else {
            lod = new int[args.length - 8];
            for (int k = 8; k < args.length; k++) {
               lod[k - 8] = arg(k, 0);
            }
         }
      case 8:
         rendering = arg(6, rendering);
         loading = arg(7, loading);
      case 6:
         fogstart = arg(4, (int) fogstart);
         fogend = arg(5, (int) fogend);
         
      case 4:
         blocksize = arg(3, blocksize);
      case 3:
         hillsize = arg(2, (int) hillsize);
      case 2:
         iterations = arg(1, iterations);
      case 1:
         mapsize = arg(0, mapsize);
      case 0:
         System.out.println("Please wait...");
      }
      
   }
   
   protected void createCustomCursor() {
      MouseInput.get().setCursorVisible( true );
    }
   
   PickResults results = new TrianglePickResults() {
      
      public void processPick() {      
      }
   };
   
}

TerraMesh has one method added - hasLockedBounds



/*
    jME Terra package. A Terrain Engine for the Java Monkey Engine.
    Copyright (C) 2005-2006,  Tijl Houtbeckers
   
    **** LICENSE: ****

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation,
   version 2.1.
   
   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.
   
   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

   **** CONTACT: ****

    I can be reached through the jmonkeyengine.com forums (user "llama"),
    or the jmonkeyengine.com contact page:
    jmonkeyengine.com/index.php?option=com_contact&task=view&contact_id=5          

*/

/**
 * @author Tijl Houtbeckers
 */

package org.llama.jmex.terra;

import java.util.Random;

import com.jme.bounding.BoundingBox;
import com.jme.renderer.Renderer;
import com.jme.scene.SceneElement;
import com.jme.scene.TriMesh;
import com.jme.scene.VBOInfo;
import com.jme.system.DisplaySystem;

public class TerraMesh extends TriMesh {

   private static final long serialVersionUID = 1L;
   
   public int id = (new Random()).nextInt();
   
   public XYKey xy;
   public static final String K_MESH="me";
   public final boolean deleteVBO;
   public final boolean ditchVBO;
   
   
   public TerraMesh(TerraData terra) {
      super(terra.terraManager.getName()+":"+terra.upperx+":"+terra.uppery);
      this.xy = terra.xy;
      deleteVBO = terra.terraProperties.useDeleteVBO;
      ditchVBO = terra.terraProperties.useDitchVBO;
      buildTerra(terra);      
   }
   
   public static VBOInfo vboinfo = null;
   
   public void buildTerra(TerraData terra) {
      terra.setTerraMesh(this);
      reconstruct(terra.verticesbuffer, terra.normalbuffer, null, terra.texturebuffer, terra.indicesbuffer);
      setDefaultColor(terra.terraProperties.defaultColor);
      setLocalTranslation(terra.origin);

      setModelBound(new BoundingBox());
        updateModelBound();
       
        this.setLocalTranslation(terra.origin);
       
       
        if (terra.terraProperties.useVBO) {
           this.setVBOInfo(new VBOInfo(true));
           if (terra.terraProperties.useIndicisVBO)
              this.getVBOInfo(0).setVBOIndexEnabled(true);           
        }
        else {
           this.setVBOInfo(new VBOInfo(false));
        }
//        this.getVBOInfo().setVBONormalEnabled(false);
       
//        if (vboinfo == null) {
//           this.setVBOInfo(vboinfo = new VBOInfo(true));
////           vboinfo.setVBOIndexEnabled(false);
//        }
        if (terra.terraProperties.useLocking) {
          this.lockBounds();
          this.lockTransforms();
        }
       
   }
   
   public boolean hasLockedBounds() {
       return ((getLocks() & SceneElement.LOCKED_BOUNDS) != 0);
   }
   
   private boolean deleted = false;
   public void deleteVBOs() {
      if (!deleteVBO)
         return;
      System.out.println("del. VBOS: "+id);
      deleted = true;
      DisplaySystem.getDisplaySystem().getRenderer().deleteVBO(this.getVertexBuffer(0));
      DisplaySystem.getDisplaySystem().getRenderer().deleteVBO(this.getNormalBuffer(0));
      getVBOInfo(0).setVBOVertexID(-1);
      getVBOInfo(0).setVBONormalID(-1);
   }
   
   private boolean ditch = false;

   public void draw(Renderer r) {
      if (deleted) {
      }
      super.draw(r);
      
      if (ditch && getVertexBuffer(0) != null) {
         this.setVertexBuffer(0,null);
         System.out.println("ditched vertex buffer("+id+"):"+this.getVBOInfo(0).getVBOVertexID());
         this.setNormalBuffer(0,null);
         System.out.println("ditched normal buffer("+id+"):"+this.getVBOInfo(0).getVBONormalID());

      }
      
      if (ditchVBO) {
         if (!ditch && this.getVertexBuffer(0) != null && this.getVBOInfo(0).getVBOVertexID() > 0
               && this.getNormalBuffer(0) != null && this.getVBOInfo(0).getVBONormalID() > 0 )
            ditch = true;
         
      }

   }
////      if (this.getVBOInfo() == null && vboinfo != null) {
////         if (vboinfo.getVBOTextureID(0) > 0){
////              VBOInfo myvbo = vboinfo.copy();
////              myvbo.setVBOTextureID(0, vboinfo.getVBOTextureID(0));
////
////              if (myvbo.isVBOIndexEnabled())
////                 myvbo.setVBOIndexID(vboinfo.getVBOIndexID());              
////              setVBOInfo(myvbo);
////           }
////      }
////      else if (this.getVBOInfo().getVBOVertexID() > 0 && this.getVertexBuffer() != null) {
////         this.setVertexBuffer(null);
////         this.setNormalBuffer(null);
//////         this.setVertexBuffer(null);
////         
//////         this.setVertexBuffer(null);
////      }
//      
//      
//      if (getVBOInfo() != null)
//            System.out.println(getVBOInfo().getVBOIndexID());
//      super.draw(r);
//   }
   
   
   
   

   
   
   
}

Lastly, one method added to TerraManager setPoint(int height, int x, int y)


/*
    jME Terra package. A Terrain Engine for the Java Monkey Engine.
    Copyright (C) 2005-2006,  Tijl Houtbeckers
   
    **** LICENSE: ****

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation,
   version 2.1.
   
   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.
   
   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

   **** CONTACT: ****

    I can be reached through the jmonkeyengine.com forums (user "llama"),
    or the jmonkeyengine.com contact page:
    jmonkeyengine.com/index.php?option=com_contact&task=view&contact_id=5          

*/

/**
 * @author Tijl Houtbeckers
 */

package org.llama.jmex.terra;

import java.nio.FloatBuffer;

import com.jme.math.Vector3f;

public class TerraManager extends HeightMapManager {

    private int blocksize, mapsize, halfblocksize;

    public int getMapSize() {
        return mapsize;
    }
   
    String name;   
    public String getName() {
       return name;
    }

    public TerraManager(String name, int mapsize, int blocksize) {
        super(mapsize);
        this.name = name;
        this.mapsize = mapsize;
        this.blocksize = blocksize;
        this.halfblocksize = blocksize / 2;
    }

    public int getBlockSize() {
        return blocksize;
    }
    public static final String K_TD = "td";
   
    public TerraData getTerraData(int x, int y) {
       return (TerraData) maps.get(gkey(x, y, K_TD));
    }
    public void addTerraData(TerraData tb, int x, int y) {
       maps.put(gkey(x, y, K_TD), tb);
    }
    public TerraData removeTerraData(int x, int y) {
        return (TerraData) maps.remove(gkey(x, y, K_TD));
    }
   
    public int findMap(int point) {
       if (point < 0)
          return ((point+1) / mapsize) - 1;
       return point/mapsize;
    }
   
    public int findBlock(int point) {
       if (point < 0)
          return ((point-halfblocksize+1) / blocksize);
       
       return (point + halfblocksize)/blocksize;
    }
   
    public int getPoint(int x, int y) {
       int mapx = findMap(x);
       int mapy = findMap(y);
       return getMapWithCaching(mapx, mapy).get(
             (y-(mapy*mapsize))*mapsize+(x-(mapx*mapsize))
          );
    }
   
    public void setPoint(int height, int x, int y) {
       int mapx = findMap(x);
       int mapy = findMap(y);
       getMapWithCaching(mapx, mapy).put((y-(mapy*mapsize))*mapsize+(x-(mapx*mapsize)), height);
    }
   
   
    public Vector3f getVector3f(int x, int y, int startx, int starty, Vector3f stepScale, Vector3f ret) {
       if (ret == null)
          ret = new Vector3f();
       int h = getPoint(x, y);
        int ex = (byte) ( 0x0ff & (h >> 16) );
        int ez = (byte) ( 0x0ff & (h >> 24) );
        h &= 0x0ffff;
       
        ret.x = (x-startx+(ex/127f)) * stepScale.x;
        ret.y =  h * stepScale.y;
        ret.z = (y-starty + (ez/127f)) * stepScale.z;
        return ret;
    }
   
    public void setVector3fInBuffer(FloatBuffer buf3f, int index, int x, int y, int startx, int starty, Vector3f stepScale) {
//       int h = getPoint(x, y);
//      
//        buf3f.put(index * 3, (x-startx) * stepScale.x);
//        buf3f.put((index * 3) + 1, h * stepScale.y);
//        buf3f.put((index * 3) + 2, (y-starty) * stepScale.z);
       
        int h = getPoint(x, y);
        int ex = (byte) ( 0x0ff & (h >> 16) );
        int ez = (byte) ( 0x0ff & (h >> 24) );
        h &= 0x0ffff;
        buf3f.put(index * 3, (x-startx+(ex/127f)) * stepScale.x);
        buf3f.put((index * 3) + 1, h * stepScale.y);
        buf3f.put((index * 3) + 2, (y-starty + (ez/127f)) * stepScale.z);    
       
    }
   
    public int getBlockTopPosition(int block) {
      return (blocksize * block) - halfblocksize;
    }
   
    public int getBlockBottomPosition(int block) {
      return halfblocksize + (blocksize * block);
    }
   
    public int distanceToBlock(int block, int point) {
       return Math.min(Math.abs(getBlockTopPosition(block) - point), Math.abs(getBlockBottomPosition(block) - point));
    }
   
    public int distanceToBlock(int blockx, int blocky, int x, int y) {
       return hypot(distanceToBlock(blockx, x), distanceToBlock(blocky, y));
    }
   
    public int distanceToBlockSquared(int blockx, int blocky, int x, int y) {
       int disx = distanceToBlock(blockx, x);
       int disy = distanceToBlock(blocky, y);
       return (disx * disx) + (disy * disy);
    }
   
    public int getMapTopPosition(int map) {
      return mapsize * map;
    }
   
    public int getMapBottomPosition(int map) {
      return mapsize * (map+1);
    }
   
    public int distanceToMap(int map, int point) {
       return Math.min(Math.abs(getMapTopPosition(map) - point), Math.abs(getMapBottomPosition(map) - point));
    }
   
    public int distanceToMap(int mapx, int mapy, int x, int y) {
       return hypot(distanceToMap(mapx, x), distanceToMap(mapy, y));
    }
   
    public long distanceToMapSquared(int mapx, int mapy, int x, int y) {
       long disx = distanceToMap(mapx, x);
       long disy = distanceToMap(mapy, y);
       return (disx * disx) + (disy * disy);
    }
   
    public final static int hypot(int number1, int number2) {
//       return isqrt((number1 * number1) + (number2 * number2));
       return (int) Math.sqrt((number1 * number1) + (number2 * number2));
    }

//    public final static int isqrt(int number) {
//        int n  = 1;
//        int n1 = n + number / n >> 1;
//
//        while(Math.abs(n1 - n) > 1) {
//          n  = n1;
//          n1 = n + number / n >> 1;
//        }
//        while((n1*n1) > number) {
//          n1 -= 1;
//        }
//        return n1;
//      }
   
   
//    public int block2Map(int k) {
//        boolean turn = false;
//        if (k < 0) {
//            k=-k;
//            turn=true;
//        }
//      
//        float nbs = (getMapSize() - 1)/ (getBlockSize() - 1);
//        int mapk = (int) Math.floor((k - (nbs / 2f)) / nbs) + 1;
//        if (turn) {
//            return -mapk;
//        }
//        else
//            return mapk;
//    }
//   
//    public int block2Section(int k) {
//        boolean turn = false;
//        if (k < 0) {
//            k=-k;
//            turn=true;
//        }
//        int nbs = (getMapSize() - 1)/ (getBlockSize() - 1);
//       
//        float ek = (k - (nbs / 2f)) / nbs + 1;
//       
//        int seck= (int)Math.floor((ek - Math.floor(ek)) * nbs);
//       
//        if (turn) {
//            return nbs - 1 - seck;
//        }
//        else
//            return seck;
//    }
 
}

theprism said:

Ill post up a screenie first ( yep - this is my attempt at drawing a Llama )



How about the jME logo?

From what i can remember Terra dynamically creates terrain but does it dynamically load the map pieces that it uses to create the terrain from file also?

Awesome, theprism!

Fungi said:

From what i can remember Terra dynamically creates terrain but does it dynamically load the map pieces that it uses to create the terrain from file also?


Good point, will need to check if it has the data loaded...
hevee said:

Awesome, theprism!


Will be nice to get some descent splatting and rendering happening  ;)

:smiley: I will start working on that later this week, promised!

Llama, can you please let me know when you have done any changes. Am keen to start writing the javadocs where i can to help others understand the code more easlily.



Also, can you change the method names in terraManager to getHeightAtPoint and setHeightAtPoint