Height map coords from world vector?

The problem with this approach is that you would have to regenerate your geometry with each change to the heightmap… What I would suggest is to update both, the heightmap and the terrain mesh simultaneously (i.e. keep them sync-ed) to avoid that overhead.

I wrote a method that gets the height map index from the world position.  It does not quite work correctly because everywhere I click selects the bottom right height map coordinate, but at least it's a start.  I ran it through the debugger and noticed that the geometry batch is always a batch of a terrain block.  Is there a way to get the actual triangle intersected?  I see that you can get the triangle index, but geometry batch only lets you get the vertex buffer but cannot get a "triangle" object by index.  I am not sure of the relationship of the triangle index to the vertex index in the vertex buffer since it would depend on how the data is organized: triangle strip, indexed triangles, triangle fan, etc.?  I could try buffer[triIndex*3], but that assumes that there is no indexing done on the triangles.      I also looked at the code for TerrainPage.getHeightFromWorld:


   public float getHeightFromWorld(Vector3f position) {
       Vector3f locationPos = calcVec1.set(position).subtractLocal(
               localTranslation).divideLocal(stepScale);
       locationPos.multLocal(getStepScale());

       return getHeight(locationPos.x, locationPos.z);
   }



Why doesn't the method just call getWorldToLocal to make the transformation?  Doesn't dividing by stepscale and then multiplying by it just cancel out the operation.  It also seems to only be accounting for translation and ignores any rotations that may have been done on the terrain node.

I've posted my terrain selection code below.


   public static int getHeightMapIndexFromWorld(TerrainPage page,Vector3f worldVec,IntBuffer outRowCol)
   {
         // convert to local coords
         Vector3f pos = new Vector3f(worldVec);
         page.worldToLocal(worldVec, pos);
         
         Vector3f stepScale = page.getStepScale();
         // row is y and col is x
         int row = -1,col = -1;
         float temp;
         float fxp = pos.x;
         float fyp = pos.y;
         float fxs = stepScale.x;
         float fys = stepScale.y;
         if(fxs == 0 || fys == 0)
            throw new IllegalArgumentException("page : " + page + "; has invalid step scale : " + stepScale);
        temp = fyp / fys;
         if(!Float.isNaN(temp) && !Float.isInfinite(temp))
               row = (int)temp;
         else
         {
            logger.warning("numerical underflow or overflow calculating heightmap row");
            return -1;
         }
        
         temp = fxp / fxs;
         if(!Float.isNaN(temp) && !Float.isInfinite(temp))
            col = (int)temp;
         else
         {
            logger.warning("numerical underflow or overflow calculating heightmap col");
            return -1;
         }
        
         int size = page.getSize();
         // convert row and col to absolute map index
         // TODO: terrain block gets copy of subset of original height map, may affect coords?
         if(row > size || col > size)
         {
            logger.warning("row or col outside height map bounds: " +
                  "row= " + row + ";col=" + col + ";size=" + size);
            return -1;
         }
         // col is x and row is y
         outRowCol.put(col).put(row);
         // return the index
         return row * size + col;
       
         // original TerrainPage.getHeightFromWorld impl
         // pos.subtractLocal(
          //        page.getLocalTranslation()).divideLocal(page.getStepScale());
          // TerrainPage.getHeightFromWorld does this as well, why, wouldn't it undo the divide???
          //pos.multLocal(page.getStepScale());
   }
   private void alterTerrain(boolean raise,Vector3f worldCoord)
   {
      TerrainPage page = terrainImpl;
      coordBuf.rewind();
      int index = TerrainUtil.getHeightMapIndexFromWorld(page, worldCoord,coordBuf);
      // TODO: this could break the texturing!!??!
      
      if(index != -1)
      {
         heightMap.getHeightMap()[index] += (raise ? TERRAIN_DELTA : -TERRAIN_DELTA);
         page.setHeightMapValue(coordBuf.get(0), coordBuf.get(1), heightMap.getHeightMap()[index]);
         page.updateFromHeightMap();
      }
   }
   
   private Vector3f getWorldTerrainMousePick()
   {
      DisplaySystem display = DisplaySystem.getDisplaySystem();
      Vector2f screenPos = new Vector2f();
      // Get the position that the mouse is pointing to
      screenPos.set(mouse.getHotSpotPosition().x, mouse.getHotSpotPosition().y);
      // Get the world location of that X,Y value
      Vector3f worldCoords = display.getWorldCoordinates(screenPos, 0);
      Vector3f worldCoords2 = display.getWorldCoordinates(screenPos, 1);
        // Create a ray starting from the camera, and going in the direction
      // of the mouse's location
      Ray mouseRay = new Ray(worldCoords, worldCoords2
            .subtractLocal(worldCoords).normalizeLocal());
      // Does the mouse's ray intersect the box's world bounds?
      rayPick.clear();
      buf.rewind();
      // ray pick already initialized to order by closest to furthest intersection distance
      terrainImpl.findPick(mouseRay, rayPick);
      // intersection occurred
      if(rayPick.getNumber() > 0)
      {
         //ArrayList<Integer> tris = rayPick.getPickData(0).getTargetTris();
         rayPick.getPickData(0).getTargetMesh().getWorldCoords(buf);
         //GeomBatch batch = rayPick.getPickData(0).getTargetMesh();
         // move the buffer data to the world vector
         tmpVector.set(buf.get(0),buf.get(1),buf.get(2));
         return tmpVector;
      }
      else // no intersection
         return null;
      
   }



Thanks again.

I got it working pretty good now : kudos to jmetest.intersection.TestTrianglePick  :D  It's still a little broken because where you click is not always exactly where the terrain is manipulated (it's offset sometimes by what looks like a square or two), but that's probably because my height map finder code is still a little off since I do some integer rounding to get the indices.  And currently, I can only produce some ugly Stalactites since I am modifying a single height map value at a time, but I will expand the "brush size" so that multiple adjacent height map values are modified at the same time by a user-selected "brush size", and hopefully everything will connect nicely. I've included the interesting part of the code below…


   public static int getHeightMapIndexFromWorld(TerrainPage page,Vector3f worldVec,IntBuffer outRowCol)
   {
         // convert to local coords
         Vector3f pos = new Vector3f(worldVec);
         page.worldToLocal(worldVec, pos);
         
         Vector3f stepScale = page.getStepScale();
         // row is z and col is x
         // height map indices are always positive and start from upper left corner of terrain
         // upper left coordinate is (-size/2+1,height,size/2-1)
         int row = -1,col = -1;
         int size = page.getSize();
         float temp;
         float fxp = pos.x;
         float fzp = pos.z;
         float fxs = stepScale.x;
         float fzs = stepScale.z;
         if(fxs == 0 || fzs == 0)
            throw new IllegalArgumentException("page : " + page + "; has invalid step scale : " + stepScale);
        temp = fzp / fzs;
         if(!Float.isNaN(temp) && !Float.isInfinite(temp))
         {
               row = (int)temp + size/2 - 1;
         }
         else
         {
            logger.warning("numerical underflow or overflow calculating heightmap row");
            return -1;
         }
        
         temp = fxp / fxs;
         if(!Float.isNaN(temp) && !Float.isInfinite(temp))
         {
            col = (int)temp + size/2 - 1;
         }
         else
         {
            logger.warning("numerical underflow or overflow calculating heightmap col");
            return -1;
         }
        
         // convert row and col to absolute map index
         if(row > size || col > size)
         {
            logger.warning("row or col outside height map bounds: " +
                  "row= " + row + ";col=" + col + ";size=" + size);
            return -1;
         }
         // col is x and row is y
         outRowCol.put(col).put(row);
         // return the index
         return row * size + col;
   }
   private void alterTerrain(boolean raise,Vector3f worldCoord)
   {
      TerrainPage page = terrainImpl;
      coordBuf.rewind();
      int index = TerrainUtil.getHeightMapIndexFromWorld(page, worldCoord,coordBuf);
      
      if(index != -1)
      {
         heightMap.getHeightMap()[index] += (raise ? TERRAIN_DELTA : -TERRAIN_DELTA);
         page.setHeightMapValue(coordBuf.get(0), coordBuf.get(1), heightMap.getHeightMap()[index]);
         page.updateFromHeightMap();
      }
   }
   
   private Vector3f getWorldTerrainMousePick()
   {
      DisplaySystem display = DisplaySystem.getDisplaySystem();
      Vector2f screenPos = new Vector2f();
      // Get the position that the mouse is pointing to
      screenPos.set(mouse.getHotSpotPosition().x, mouse.getHotSpotPosition().y);
      // Get the world location of that X,Y value
      Vector3f worldCoords = display.getWorldCoordinates(screenPos, 0);
      Vector3f worldCoords2 = display.getWorldCoordinates(screenPos, 1);
                // Create a ray starting from the camera, and going in the direction
      // of the mouse's location
      Ray mouseRay = new Ray(worldCoords, worldCoords2
            .subtractLocal(worldCoords).normalizeLocal());
      // Does the mouse's ray intersect the box's world bounds?
      rayPick.clear();
      buf.rewind();
      // ray pick already initialized to order by closest to furthest intersection distance
      terrainImpl.findPick(mouseRay, rayPick);
      // intersection occurred
      if(rayPick.getNumber() > 0)
      {
         // Kudos to jmetest.intersection.TestTrianglePick
         //ArrayList<Integer> tris = rayPick.getPickData(0).getTargetTris();
         PickData pickData = rayPick.getPickData(0);
         TriangleBatch mesh = (TriangleBatch)pickData.getTargetMesh();
         List<Integer> tris = pickData.getTargetTris();
         
         // only process the first triangle interected, should only be one on terrain anyways
         if(!tris.isEmpty())
         {
            // store the vertices of the triangle
            Vector3f[] verts = new Vector3f[3];
            mesh.getTriangle(tris.get(0), verts);
            // TODO: probably can circumvent some of this and avoid some localToVWorld calls
            // get the local to VWorld by accessing the batch's parent geometry
            Geometry meshParent = mesh.getParentGeom();
            
            
            // translate the given verts to world coordinates
            // first scale,then rotate,then translate
                               // TODO: determine the "goodness" of this approach
                              // and take avg of the vertex positions
                    Vector3f avgVec = new Vector3f();
                              for ( Vector3f v : verts )
                              {
                                 v.multLocal(meshParent.getWorldScale());
                                 meshParent.getWorldRotation().multLocal(v);
                                 v.addLocal(meshParent.getWorldTranslation());
                                 avgVec.addLocal(v);
                             }
                             avgVec.divideLocal(verts.length);
                              return avgVec;

         }
      }
      // no intersection
      return null;
      
   }
   // action events for modifying the terrain
   private class TerrainInputAction implements InputActionInterface
   {
      public void update(float dt)
      {
         MouseInput mi = MouseInput.get();
         if(mi.isButtonDown(MouseButtonBinding.LEFT_BUTTON))
         {
            Vector3f worldPick = getWorldTerrainMousePick();
            if(worldPick != null)
               alterTerrain(true,worldPick);
         }
         else if(mi.isButtonDown(MouseButtonBinding.RIGHT_BUTTON))
         {
            Vector3f worldPick = getWorldTerrainMousePick();
            if(worldPick != null)
               alterTerrain(false,worldPick);
         }
      }
      /* (non-Javadoc)
       * @see com.jme.input.action.InputActionInterface#performAction(com.jme.input.action.InputActionEvent)
       */
      @Override
      public void performAction(InputActionEvent evt)
      {
         // TODO: implement if want to listen for keystrokes
      }
      
   }

I know this is an old post but I've been trying to do something like this for a while and not getting anywhere. What I need to do is what Duenez suggested. I need to select an area of the terrain using the mouse and directly change the terrain mesh instead of modifying the height map and regenerating the terrain from that. I can get the triangle that the mouse picked, but only a copy of the triangle is returned, so when I modify the triangle's vertices the terrain doesn't change at all. Does anyone know how I can do this?



Thanks.