ImageBasedHeightMap or TerrainQuad seams

I have taken the HelloTerrain file and changed it to load my own (1024 x 1024 pixel) height map. It worked but:

  1. it has seams that do not match each other. Four seams meet right under my starting location.
  2. The mountains look good, until I move, then they flatten out.

    Then I added some collision detection from HelloCollision. My player falls until it lands in the middle of the seams. 3) Then, when I move, it falls through the seams!

    Is there a fix for this? If the fix is in the nightly build, is there a Wiki page on how to set up jMP to use the svn copy instead of the realease copy?

    Here is the code:

    [java]package mygame;

    import jme3tools.converters.ImageToAwt;


    import com.jme3.bounding.BoundingBox;

    import com.jme3.bullet.BulletAppState;

    import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;

    import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;

    import com.jme3.bullet.nodes.PhysicsCharacterNode;

    import com.jme3.bullet.nodes.PhysicsNode;

    import com.jme3.font.BitmapText;

    import com.jme3.input.KeyInput;

    import com.jme3.input.controls.ActionListener;

    import com.jme3.input.controls.KeyTrigger;

    import com.jme3.light.PointLight;

    import com.jme3.material.Material;

    import com.jme3.math.ColorRGBA;

    import com.jme3.math.Vector3f;

    import com.jme3.renderer.Camera;

    import com.jme3.scene.Geometry;

    import com.jme3.scene.Spatial;

    import com.jme3.terrain.geomipmap.TerrainLodControl;

    import com.jme3.terrain.heightmap.AbstractHeightMap;

    import com.jme3.terrain.heightmap.ImageBasedHeightMap;

    import com.jme3.terrain.geomipmap.TerrainQuad;

    import com.jme3.texture.Texture;

    import com.jme3.texture.Texture.WrapMode;

    import java.util.ArrayList;

    import java.util.List;

    public class Main extends SimpleApplication implements ActionListener {

    private TerrainQuad terrain;

    private Material matRock;

    private Material matWire;

    private boolean wireframe = true;

    protected BitmapText hintText;

    private PointLight pl;

    private Geometry lightMdl;

    private BulletAppState bulletAppState;

    private Spatial sceneModel;

    private PhysicsNode landscape;

    private PhysicsCharacterNode player;

    private Vector3f walkDirection = new Vector3f();

    private boolean left = false, right = false, up = false, down = false;

    public static void main(String[] args) {

    Main app = new Main();




    public void initialize() {





    public void simpleInitApp() {

    bulletAppState = new BulletAppState();


    viewPort.setBackgroundColor(new ColorRGBA(0.7f,0.8f,1f,1f));

    // We reuse the fly camera for rotation while position is handled by physics.



    // First, we load up our textures and the heightmap texture for the terrain

    // TERRAIN TEXTURE material

    matRock = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");

    // ALPHA map (for splat textures)

    matRock.setTexture("m_Alpha", assetManager.loadTexture("Textures/Terrain/splat/ireland-am.png"));

    // HEIGHTMAP image (for the terrain heightmap)

    Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/ireland-hm.png");

    // DIRT texture

    Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");


    matRock.setTexture("m_Tex1", dirt);

    matRock.setFloat("m_Tex1Scale", 32f);

    // GRASS texture

    Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");


    matRock.setTexture("m_Tex2", grass);

    matRock.setFloat("m_Tex2Scale", 2048f);

    // WATER texture

    Texture water = assetManager.loadTexture("Textures/Terrain/splat/water.jpg");


    matRock.setTexture("m_Tex3", water);

    matRock.setFloat("m_Tex3Scale", 8f);

    // WIREFRAME material

    matWire = new Material(assetManager, "Common/MatDefs/Misc/WireColor.j3md");

    matWire.setColor("m_Color", ColorRGBA.Green);


    AbstractHeightMap heightmap = null;

    try {

    heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 2f);

    //Try heightmap.load(false, false);


    } catch (Exception e) {



  • Here we create the actual terrain. The tiles will be 65x65, and the total size of the
  • terrain will be 513x513. It uses the heightmap we created to generate the height values.


  • Optimal terrain patch size is 65 (64x64)
  • If you go for small patch size, it will definitely slow down because the depth of
  • the quad tree will increase, and more is done on the CPU then to traverse it.
  • I plan to give each node in the tree a reference to its neighbours so that should
  • resolve any of these slowdowns. -Brent

  • The total size is up to you. At 1025 it ran fine for me (200+FPS), however at
  • size=2049, it got really slow. But that is a jump from 2 million to 8 million triangles…


    terrain = new TerrainQuad("terrain", 65, 1025, heightmap.getHeightMap());

    List<Camera> cameras = new ArrayList<Camera>();


    TerrainLodControl control = new TerrainLodControl(terrain, cameras);



    terrain.setModelBound(new BoundingBox());


    terrain.setLocalTranslation(0, -1, 0);

    terrain.setLocalScale(64f, 1f, 64f);

    // We set up collision detection for the scene by creating a

    // compound collision shape and a physics node.

    // CompoundCollisionShape sceneShape =

    // CollisionShapeFactory.createMeshCompoundShape(terrain);

    HeightfieldCollisionShape sceneShape =

    new HeightfieldCollisionShape (heightmap.getHeightMap());

    landscape = new PhysicsNode(terrain, sceneShape, 0);

    // We set up collision detection for the player by creating

    // a capsule collision shape and a physics character node.

    // The physics character node offers extra settings for

    // size, stepheight, jumping, falling, and gravity.

    // We also put the player in its starting position.

    player = new PhysicsCharacterNode(new CapsuleCollisionShape(1.5f, 6f, 1), .05f);




    player.setLocalTranslation(new Vector3f(0, 500, 0));

    // We attach the scene and the player to the rootnode and the physics space,

    // to make them appear in the game world.





    // rootNode.attachChild(terrain);

    getCamera().getLocation().y = 500;

    getCamera().setDirection(new Vector3f(0, 0, 0));


    public void loadHintText() {

    hintText = new BitmapText(guiFont, false);


    hintText.setLocalTranslation(0, getCamera().getHeight(), 0);

    hintText.setText("Hit T to switch to wireframe");



    private void setupKeys() {


    inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));

    inputManager.addListener(this, "wireframe");

    inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));

    inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));

    inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));

    inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));

    inputManager.addMapping("Jumps", new KeyTrigger(KeyInput.KEY_SPACE));

    inputManager.addListener(this, "Lefts");

    inputManager.addListener(this, "Rights");

    inputManager.addListener(this, "Ups");

    inputManager.addListener(this, "Downs");

    inputManager.addListener(this, "Jumps");


    public void onAction(String name, boolean pressed, float tpf) {

    if (name.equals("wireframe") && !pressed) {

    wireframe = !wireframe;

    if (!wireframe) {


    } else {



    } else if (name.equals("Lefts")) {

    if (pressed) { left = true; } else { left = false; }

    } else if (name.equals("Rights")) {

    if (pressed) { right = true; } else { right = false; }

    } else if (name.equals("Ups")) {

    if (pressed) { up = true; } else { up = false; }

    } else if (name.equals("Downs")) {

    if (pressed) { down = true; } else { down = false; }

    } else if (name.equals("Jumps")) {




  • This is the main event loop–walking happens here.
  • We check in which direction the player is walking by interpreting
  • the camera direction forward (camDir) and to the side (camLeft).
  • The setWalkDirection() command is what lets a physics-controlled player walk.
  • We also make sure here that the camera moves with player.



    public void simpleUpdate(float tpf) {

    Vector3f camDir = cam.getDirection().clone().multLocal(0.6f);

    Vector3f camLeft = cam.getLeft().clone().multLocal(0.4f);

    walkDirection.set(0, 0, 0);

    if (left) { walkDirection.addLocal(camLeft); }

    if (right) { walkDirection.addLocal(camLeft.negate()); }

    if (up) { walkDirection.addLocal(camDir); }

    if (down) { walkDirection.addLocal(camDir.negate()); }






Press F1 to read the manual on updating to nightly.

Ok, updated to nightly build. The seams are still there. And the player is still falling through them.

So most probably theres something wrong with the image. Is the image a grayscale image? What format is it in (jpeg is not good due to the noise)? Try exporting it in another format.

The ireland-hm.png file is a grayscale height map in png format with the compression set to 0 (in other word, its a large 426.7 KB file). When I save it to a gif format and load that it still has empty seams.