Another noobie question, finding getting location on terrain

I want someting relatively simple. When i click on my mouse i want the location on the terrain that i clicked on. So i implemented picking in my program. I used TrianglePicking because i needed the location of the triangle that i click on, not the bounding box of the terrain. My code is simple enough, i think.

class ClickPickResults extends TrianglePickResults{
   public void processPick(){
      System.out.println(getNumber());
   }
}



and

class GameMouseListener implements MouseInputListener{
   private Camera cam = GameWorld.getDisplay().getRenderer().getCamera();
   private HoverResults hoverresults = new HoverResults();
   private ClickPickResults clickresults = new ClickPickResults();
   private Node node = GameWorld.getNode();
   private Vector2f screenpos = new Vector2f();
   private Vector3f worldcoords;
   private Ray mouseray = new Ray();
   
   
   public void onButton(int button, boolean pressed, int x, int y) {
      if (button == 0){
         if (!pressed){
            recalcHighLightedUnit(x,y);
            if (LocalPlayer.getLocalPlayer().getHighlightedUnit()==null){
               System.out.println("click");
               mouseray.setOrigin(cam.getLocation());
               mouseray.setDirection(worldcoords.subtractLocal(mouseray.getOrigin()));
               clickresults.clear();
               node.calculatePick(mouseray, clickresults);
            }
         }
      }
   }


when i try this i get the following exception


java.lang.OutOfMemoryError: Java heap space
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Exception in thread "Thread-1" java.lang.OutOfMemoryError: Java heap space



I am guessing that this because the terrain has too many triangles. What would be a better way of getting what i want?

Hi!

If your terrain is from blocks, you could make smaller blocks, and clear out (i think its called) the collision tree from blocks unlike the player will click on.



An idea for picking terrain:

  1. Get the nearest block by picking only the bounding box
  2. Calculate where the ray penetrates the bounding box
  3. From there go on in the direction of the ray, and find the first point where the terrain height is bigger than the rays Y coordinate at that position
  4. If no such point is found, get the next nearest block and go to step 2



    I think this picking method would be faster, and use no extra memory.

thanks for the post!

I am wondering what step 2 would look like.

could you push a little harder please?

I use triangle pick results.  I haven't tested this on large terrains, right now I'm only using a single terrainblock.

Some of this code is mine, some of it is grabbed from elsewhere.





My code was/is messy.  I cut a lot out of this method.  I hope left enough so it works/makes sense:

I'm sure there are better ways to do this.





Steps:

1.  Make a ray.

2. Call findPick or findTrianglePick on the terrain, use the ray, and store the results

3.  Go through the results.  Grab the mesh from the result.  Grab the triangle from the mesh.

4.  Find the spot on the triangle that the ray intersects with



  Vector2f screenPos = new Vector2f();
                    // Get the position that the mouse is pointing to
  screenPos.set(am.getHotSpotPosition().x, am.getHotSpotPosition().y);
                              
         // Get the world location of that X,Y value
         Vector3f worldCoords = client.getDisplay().getWorldCoordinates(screenPos, 1f);
         // Create a ray starting from the camera, and going in the direction
         // of the mouse's location
         Ray mouseRay = new Ray(client.cam.getLocation(), worldCoords
               .subtractLocal(client.cam.getLocation()));
         
         pick.clear();
                                                          
         terrain.findPick(mouseRay, pick);
                                                       
                            for (int i = 0; i < pick.getNumber(); i++) {
         
                            PickData pData = pick.getPickData(i);
               ArrayList tris = pData.getTargetTris();
                                        TriMesh mesh = (TriMesh) pData.getTargetMesh();
                                        int[] indices = new int[3];
                                        Vector3f [] verticies = new Vector3f[3];
                                    if(tris.size() < 1)
                                            continue;
                                       
                                  mesh.getTriangle( ((Integer)tris.get(0)).intValue(), indices);
                                  mesh.getTriangle( ((Integer)tris.get(0)).intValue(), verticies);
                                 
                     
                                       
                              
                                  //check where the ray hits the triangle? get xy from that?
                                  Vector3f target = new Vector3f();
                                  mouseRay.intersectWhere(verticies[0], verticies[1], verticies[2], target);
                                 
 
                                 x = target.x;
                                 y = target.z;
                                   
                   
                          
                            }
                        //     System.out.println("Picked: (" + x + ", " + y + ")");
                            
                 
                            
                            if ( x == 0 && y == 0)
                            {
                                //probably was a bad pick so ignore
                                return;
                            }
                               
                     //here is where i would tell my character to walk to x y

currently i am using only 1 terrain block, once i get home i check how many triangles it contains.

The problem comes from doing "findpick" in the node.

I'll try the different methods once i get home.



Thanks everyone!

The problem with out of memory comes from terrain mesh’s collision tree being generated takes up memory. I only have it after 20 or so blocks. So you might have some other issue, or an absolutely huge block.



I found this on the subject: Grid Tracing



Its the same algorithm i had in mind.

ok i can't help but think that i am using statistics incorrectly, the number of meshes, vertices and triangles keep on skyrocketing, even though i don't think im doing anything to the scene graph. All i need to do is enable statistics then use getStatistics… right?

Well how big is your terrain? What is the size of the heightmap?

the height map is a bmp that is 256x256

the terrain is constructed with the following

      try {
         heightMap = new ImageBasedHeightMap(ImageIO.read(new File("map/terrainheight.bmp")));
      } catch (IOException e) {
         // TODO Auto-generated catch block
         System.out.println("Error!: "+e);
      }
       Vector3f terrainScale = new Vector3f(10,1,10);
       tb = new TerrainBlock("Terrain", heightMap.getSize(), terrainScale,
                                          heightMap.getHeightMap(),
                                          new Vector3f(0, 0, 0), false);

Hi!

256x256 could be too big unless you have some newer graphics card. Try using TerrainPage with 257x257 heightmap, and 129x129 (or smaller) blocksize. In terms of performance bigger blocks are better, but graf card is limited on how big can handle. Out of memory error is most probably because the single big block too. Try making the blocksize a configuration parameter, if the user has modern graf card and lots of RAM, he can choose bigger blocksize, if not, he can use smaller blocks and have the CPU more work to do.

awesome vear!

thanks for helping me out with this, i'll try to implement the fix later today

your what makes jme so great  :smiley:

vear said:

Hi!
If your terrain is from blocks, you could make smaller blocks, and clear out (i think its called) the collision tree from blocks unlike the player will click on.

An idea for picking terrain:
1. Get the nearest block by picking only the bounding box
2. Calculate where the ray penetrates the bounding box
3. From there go on in the direction of the ray, and find the first point where the terrain height is bigger than the rays Y coordinate at that position
4. If no such point is found, get the next nearest block and go to step 2

I think this picking method would be faster, and use no extra memory.



Hi,

I tried your algorithm and it works perfectly (I'm using a simple dichotomy search for the collision point along the ray), at least on a single block. Very fast too, often less than 10 iterations for 0.01 (absolute delta, not percent) precision in height.
Thanks for the idea ;)

(If anyone is interested in the code, please let me know)
drfugly said:

ok i can't help but think that i am using statistics incorrectly, the number of meshes, vertices and triangles keep on skyrocketing, even though i don't think im doing anything to the scene graph. All i need to do is enable statistics then use getStatistics... right?


You need to call clearStatistics once per game loop too I believe (look in simplegame example).
The Librarian said:


(If anyone is interested in the code, please let me know)


Congrats on implementing the algorithm. Can you post the code?

Here it is :



...
BoundingPickResults pr = new BoundingPickResults();
pr.clear();
pr.setCheckDistance(true);
rootNode.findPick(mouseRay, pr);
...
// Check if there are picking results, then iterate over them
...

Ray r = pr.getPickData(i).getRay();


// Compute the distance between the camera and the collision point on the top of the terrain bounding volume
float minHeight = camera.z - ((BoundingBox) tb.getModelBound()).zExtent * 2;
float minDist = FastMath.abs(minHeight / r.getDirection().normalize().z);

// Compute the distance between the camera and the collision point on the bottom of the terrain bb
float maxHeight = camera.z;
float maxDist = FastMath.abs(maxHeight / r.getDirection().normalize().z);


// We now perform a Newton search along the ray, between minDist and maxDist
float delta = maxDist - minDist;
float dist = minDist + delta / 2;
boolean found = false;

Vector3f target;
float x, y, z;
// Counter to avoid infinite loop or heap errors
int count = 0;

do {
        // get the point along the ray at distance dist from camera
   target = r.getDirection().normalize().mult(dist);
       // change to world coordinates
   x = target.x + camera.x;
   y = target.y + camera.y;
   z = target.z + camera.z;

        // compute the height of the point right below target
   float realHeight = tb.getHeight(new Vector2f(x + tb.getSize()/2, tb.getSize()/2 - y));

       // if difference in height is lower than 0.01 (arbitrary), stop here
   if (FastMath.abs(z - realHeight) < 0.01) found = true;

     
   if (Float.isNaN(realHeight) || (z < realHeight)) {
      // target is below terrain
      delta = dist - minDist;
      maxDist = dist;
      dist = dist - delta / 2;
   }
   else {
      // target is above terrain
      delta = maxDist - dist;
      minDist = dist;
      dist = dist + delta / 2;
   }
   count++;
} while (!found && (count < 20));



Resulting coordinates are (x,y,z).
I hope the comments are enough to make this work ;)

If someone is interested in this, I updated my code to make it work with current CVS and with a terrain page.



As a side note, I put this in a pick() method in a class which extends TerrainPage. Do you think it's the right thing to do, i.e. how would jME do it if it had this feature? By the way, if you want to include this in jME I'd be flattered :wink: