Combining multiple heightmaps/sewing them together?

I’ve been doing a terrain editor in jME3, but I’m having trouble when the region fields meet up:





I ripped the BufferGeomap, and created a more straight forward version of a heightmap.

Obviously, I’ll need the surrounding data implemented in this class somehow, but which method do I need to mess with?



package com.conquest.terrain;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;

/**
 * A height map is a 2d representation of a 3d terrain. It's constructed from an array of height values.
 */
public class HeightMap extends Mesh {

   /**
    * The terrain scaling factor.
    */
   public static final float SCALE = 1f;

   /**
    * The scaling factor used when a mesh is produced from a geometry map.
    */
   public static final Vector3f TERRAIN_SCALE = new Vector3f(SCALE, SCALE / 10000f, SCALE);

   /**
    * The texture scaling factor used when a mesh is produced.
    * How many textures should cover the terrain?
    * Obviously one. (1x1)
    */
   public static final Vector2f TEXTURE_SCALE = new Vector2f(1f, 1f);

   /**
    * The width of this map.
    */
   private final int width;
   
   /**
    * The height of this map.
    */
   private final int height;
   
   /**
    * The integer array holding height values.
    */
   private final int[] data;

   /**
    * Constructs a new heightmap.
    * @param width The width of the heightmap.
    * @param height The height of the heightmap.
    * @param data The height data.
    * @throws IllegalArgumentException if data.length != width * height + 1
    */
   public HeightMap(int width, int height, int[] data) {
      super();
      if(data.length != width * height) {
         throw new IllegalArgumentException("Invalid data size: " + data.length + " should have been: " + (width * height));
      }
      this.width = width;
      this.height = height;
      this.data = data;
      /*
       * Should this piece of code be called from a different thread, for performance improments?
       */
      setBuffer(Type.Position, 3, createVertexBuffer(TERRAIN_SCALE));
      setBuffer(Type.Normal, 3, createNormalBuffer(TERRAIN_SCALE));
      setBuffer(Type.TexCoord, 2, createTextureCoordinateBuffer(TEXTURE_SCALE));
      setBuffer(Type.Index, 3, createIndexBuffer());
      setStatic();
      updateBound();
   }

   /**
    * Gets the height value of a specific coordinate set.
    * @return The height value of a specific coordinate set.
    */
   public int getValue(int x, int y) {
      return getValue(y*width+x);
   }

   /**
    * Gets the height value of a specific index.
    * @return The height value of a specific index.
    */
   public int getValue(int index) {
      return data[index];
   }

   /**
    * Gets the width of this map.
    * @return The width of this map.
    */
   public int getWidth() {
      return width;
   }

   /**
    * Gets the height of this map.
    * @return The height of this map.
    */
   public int getHeight() {
      return height;
   }

   /**
    * Gets the height data of this map.
    * @return The height data of this map.
    */
   public int[] getData() {
      return data;
   }

   public FloatBuffer createTextureCoordinateBuffer(Vector2f scale){
      FloatBuffer store = BufferUtils.createFloatBuffer(width * height * 2);
      for (int y = 0; y < height; y++){
         for (int x = 0; x < width; x++){
            float tcx = (float)x / (float)width;
            float tcy = (float)y / (float)height;
            store.put(tcx * scale.x);
            store.put(tcy * scale.y);
         }
      }
      return store;
   }

   /**
    * Creates a float buffer containing all the corner points for this map.
    */
   public FloatBuffer createVertexBuffer(Vector3f scale) {
      FloatBuffer store = BufferUtils.createFloatBuffer(width * height * 3);
      int index = 0;
      for (int z = 0; z < height; z++){
         for (int x = 0; x < width; x++){
            store.put(x * scale.x);
            store.put(data[index++] * scale.y);
            store.put(z * scale.z);
            
            if(x == 0) {
               //G
            }
         }
      }
      return store;
   }

   /*
    * (non-Javadoc)
    * Subclasses are encourged to provide a better implementation
    * which directly accesses the data rather than using getNormal
    */
   public FloatBuffer createNormalBuffer(Vector3f scale) {                
      FloatBuffer store = BufferUtils.createFloatBuffer(width * height * 3);
      Vector3f oppositePoint = new Vector3f();
      Vector3f adjacentPoint = new Vector3f();
      Vector3f rootPoint = new Vector3f();
      Vector3f tempNorm = new Vector3f();
      int normalIndex = 0;

      for (int y = 0; y < height; y++) {
         for (int x = 0; x < width; x++) {
            rootPoint.set(x, getValue(x,y), y);
            if (y == height - 1) {
               if (x == width - 1) { // case #4
                  // left cross up
                  adjacentPoint.set(x, getValue(x,y-1), y-1);
                  oppositePoint.set(x-1, getValue(x-1, y), y);
               } else { // case #3
                  // right cross up
                  adjacentPoint.set(x+1, getValue(x+1,y), y);
                  oppositePoint.set(x, getValue(x,y-1), y-1);
               }
            } else {
               if (x == width - 1) { // case #2
                  // left cross down
                  adjacentPoint.set(x-1, getValue(x-1,y), y);
                  oppositePoint.set(x, getValue(x,y+1), y+1);
               } else { // case #1
                  // right cross down
                  adjacentPoint.set(x, getValue(x,y+1), y+1);
                  oppositePoint.set(x+1, getValue(x+1,y), y);
               }
            }

            tempNorm.set(adjacentPoint).subtractLocal(rootPoint).crossLocal(oppositePoint.subtractLocal(rootPoint));
            tempNorm.multLocal(scale).normalizeLocal();
            BufferUtils.setInBuffer(tempNorm, store, normalIndex++);
         }
      }
      return store;
   }

   public IntBuffer createIndexBuffer(){       
      int faceN = (width-1)*(height-1)*2;
      IntBuffer store = BufferUtils.createIntBuffer(faceN*3);

      int i = 0;
      for (int z = 0; z < height-1; z++){
         for (int x = 0; x < width-1; x++){
            store.put(i).put(i+width).put(i+width+1);
            store.put(i+width+1).put(i+1).put(i);
            i++;

            // TODO: There's probably a better way to do this..
            if (x==width-2) {
               i++;
            }

         }
      }
      store.flip();
      return store;
   }

}



Thoughts: I could combine alot of those regions as one huge heightmap, but I'd be better off with small blocks, culling away. (Heigher FPS results)

Thanks in advance.

The Geomap class lets you get a "sub" geomap, so you can alter the primary geomap in your editor, and then update the separate tiles through the subgeomaps.

Momoko_Fan said:

The Geomap class lets you get a "sub" geomap, so you can alter the primary geomap in your editor, and then update the separate tiles through the subgeomaps.


My hover / lower method was bugged.. :D I finally managed to fix it. (:

- Mikkel