So last time we got our Java application up and running with a scene displayed using the JMonkeyEngine (JME) game engine. This time, we’re going to extend that to generate some procedural terrain.

We are going to start with a HeightProvider class that is used to provide us with heights of the terrain at a given point. Now, bear in mind we are aiming to ultimately have an infinite terrain to fly over, so we want our HeightProvider to account for that.

In is simplest form, the HeightProvider is an interface that has one method.

public interface HeightProvider {

  public float getHeight(float x, float y);
}

A quick note here, I am using floats as that is the preferred type for JME, most likely since that is the preferred type of OpenGL.

So for this code, I’m going to create some little util methods on the interface so we can chain the implementations and create some useful features.

public interface HeightProvider {

  /**
   * Return the height of the map at a given x,y point.
   */
  public float getHeight(float x, float y);

  /**
   * Scale out the x,y co-ords, i.e. spread the map out further
   */
  public default HeightProvider scale(float scale) {
    return (x, y) -> this.getHeight(x * scale, y * scale);
  }

  /**
   * Multiply the height so we get taller points 
   */
  public default HeightProvider mul(float mul) {
    return (x, y) -> this.getHeight(x, y) * mul;
  }

  /**
   * Create a height provider based on the JME noise functions.
   */
  public static HeightProvider noise() {
    return (x, y) -> ImprovedNoise.noise(x, y, 87);
  }
}

This gives us a HeightProvider instance on which we can call the getHeight() method repeatedly and we should get the same value back. This is essential. We aren’t just returning random numbers, we are generating some pseudo random numbers, usually based on noise.

JME comes with some classes to help with specifying terrain using height map and terrain classes. You can see more about them here. We will use the TerrainQuad class for now to help us get up and running.

We’re going to add one more method to our HeightProvider to get it to produce an array of float heights for our map. This will allow us to use our HeightProvider to generate he values for the TerrainQuad.

public interface HeightProvider {
...
  public default float[] toFloats(float x, float y, int size) {
    final float[] heightData = new float[size * size];
    for (int ix = 0; ix < size; ix++) {
      for (int iy = 0; iy < size; iy++) {
        heightData[(ix * size) + iy] = getHeight(x + ix, y + iy);
      }
    }
    return heightData;
  }
...
}

We are assuming a square terrain tile is being used. The x and y coordinates that we pass in indicate the start point of the tile. Remember, this terrain could go on for miles. We plug this coordinate into the height provider to give us a height for that point which we store in the array.

Now we have our height provider in place, lets add some code to our main class to use it!

From our first example, we will keep our code to move the camera, add the light, and create the material. We will change the code to create the geometry and we may tweak the position of the camera a bit:

  private static final int MAP_SIZE = 513;
  private static final int TILE_SIZE = 65;

  @Override
  public void simpleInitApp() {

    // set how fast WASD keys move us over the landscape.
    flyCam.setMoveSpeed(10);

    // move up, to the side and look at the origin
    getCamera().setLocation(new Vector3f(0, 12, 30));
    getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);

    // lets put some light in
    final DirectionalLight sun = new DirectionalLight(new Vector3f(0.2f, -0.6f, -0.5f).normalize());
    sun.setColor(ColorRGBA.White);
    rootNode.addLight(sun);

    final Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
}

So far so good, not too much different from before. Since we might have some hills, the camera has been moved up a bit, and pushed back through the screen so when we look at the origin we aren’t looking straight down. We also set the fly cam speed so we can move through our scene at a fairly fast pace.

Now to add the geometry. We want to create a new TerrainQuad and assign it the float values from our height provider. For now we will keep it simple and not worry about tiling and LOD. I’ve added a couple of constants at the top to indicate the tile size and heightmap size we are using. These must be (2^n)+1 values (9,17,33,65,129,257,513) with the tile size usually being less. Again, JME docs on the TerrainQuad can tell you all about that. On to the code!

    final HeightProvider provider = HeightProvider.noise();
    final float[] data = provider.toFloats(0, 0, MAP_SIZE);
    final TerrainQuad q = new TerrainQuad("terrain", TILE_SIZE, MAP_SIZE, data);

If you run this now, you are probably going to be pretty disappointed. No rolling hills and sweeping valleys, it just looks like the plains of Hoth:

Hoth…. In Space

So whats going on here, then. Well, 2 things are wrong with this. Our height provider is asked for values on integer steps, coordinates are requested of the form (0,0), (0,1), (0,2)….(1,0), (1,1), (1,2).

For this implementation, and I believe every Perlin Noise implementation, at the integer boundaries, the value is always zero. The other problem is that noise returns small values between -1 and 1 which is a really small height in our landscape.

However, those nice helper functions can fix this for us. We want to take our provider, then scale down the co-ordinates so rather than range from 0-513, they range from 0 a smaller number with interpolations in between. Change the provider constructor to the following:

final HeightProvider provider = HeightProvider.noise().scale(0.2f).mul(10);

This creates our noise provider, and then the scale method creates a new one based on that, which scales the values going into it, so if we called this provider with (47,24), those values would get scaled down to (2.35, 1.2) and those values would be passed into the noise function. Since they are not on integer boundaries, the noise function will return a non-zero value. Hurrah! However, this is still a really small value, and while you will see some hills, they will be very small. So, we want to multiply the returned height to create bigger peaks which we can do with the mul method. That returns a provider that takes the value from the previous one and multiplies the value by 10. What do you get now?

I’ll wait here while you fly around for half an hour…..

You can play around with the scale and mul parameters, keeping them in the same proportion generates a similar landscape, it will just be scaled up or down proportionally. If you decrease just the scale it stretches the map out and the hills look shorter. Increase the mul parameter and the hills will grow taller and shorter.

Also, as you ramp the values up and down, you find you go through your landscape faster or slower. You can change the flyCam speed to fix that.

Note that you only have 513 X 513 quads in this scene, so you can’t stretch the map out forever as the number of quads in the geometry will decrease and your hills will just end up a sharp triangle. You can always increase the MAP_SIZE, just remember to keep it as multiples of (2^n)+1.

In part 3, we will look at producing a better landscape using more complex height provider implementation and layered noise.