TerrainBlock, TerrainPage, and Heightmaps

My project's current milestone is to develop a working map-loading system. For this I need to know how to effectively load heightmaps. I would like to put this function into a Factory method to make the main code more concise.



Something important about my program is that it uses several heightmaps in a square (they all match up since they were cut from one image). I need to load each one of those heightmaps into some sort of Terrain object (TerrainBlock or TerrainPage sound right, but I don't know). Then I need to be able to set their translation so that they line up. This is easy enough–they are 256x256, so I just have to put them in a grid and multiply each of their coordinates by 256, and I'll have their local translation.



I want each heightmap to be culled if it's not in view, so I figure placing it on a TerrainPage or TerrainBlock will do that.



I don't know what a TerrainBlock or a TerrainPage are, they just SOUND right, so could somebody point me to some literature that explains what they are in-depth and something that explains how to use them?

if u r just concerned about culling, dont worry about it.



u dont need to cut ur height maps into pieces. just load it at once using terrain page. it will form the quad tree for u for very fast culling.

I'm also concerned with expandability. I need to be able to just drop extra maps in the folder and have the program assign them their own TerrainBlocks or TerrainPages or whatever it needs to be (still don't know :)) and place them in the appropriate place.

I've been searching the Wiki and can't figure out how to put in a heightmap that I already have onto a TerrainBlock or TerrainPage, nor have I found out which does what. Any help with this would be amazing :slight_smile:

From studying MrCoder's TestIsland, I've attempted to create a terrainblock and load it into my program.


package fyrestoneclient;

import com.jme.app.BaseGame;
import com.jme.bounding.BoundingSphere;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.shape.Sphere;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.Timer;
import com.jmex.terrain.TerrainPage;
import com.jmex.terrain.util.RawHeightMap;
import java.util.ArrayList;

public class FyrestoneClient extends BaseGame
{
    /*Screen Resolution Variables*/
    int width, height, depth, freq;
    boolean fullscreen;
    /*End Screen Resolution Variables*/
   
    private Node scene;
   
    private Camera cam;
   
    protected Timer timer;
   
    public static void main(String args[])
    {
        FyrestoneClient app = new FyrestoneClient();
        app.setDialogBehaviour(app.FIRSTRUN_OR_NOCONFIGFILE_SHOW_PROPS_DIALOG);
        app.start();
    }

    @Override
    protected void update(float interpolation)
    {
        timer.update();
        interpolation = timer.getTimePerFrame();
        if(KeyBindingManager.getKeyBindingManager().isValidCommand("exit"))
            finished = true;
    }

    @Override
    protected void render(float interpolation)
    {
        display.getRenderer().clearBuffers();
        display.getRenderer().draw(scene);
    }

    @Override
    protected void initSystem()
    {
        //Store Screen Resolution Info
        width = properties.getWidth();
        height = properties.getHeight();
        depth = properties.getDepth();
        freq = properties.getFreq();
        fullscreen = properties.getFullscreen();
        //End Screen Resolution Storage
       
        //Set up screen and camera
        try
        {
            display = DisplaySystem.getDisplaySystem(properties.getRenderer());
            display.createWindow(width, height, depth, freq, fullscreen);
            cam = display.getRenderer().createCamera(width, height);
        }
        catch (JmeException e)
        {
            e.printStackTrace();
            System.exit(1);
        }
        //End setup for screen and camera
       
        //Background color
        display.getRenderer().setBackgroundColor(ColorRGBA.black);
        //End Background color
       
        //Setting up camera to look at the terrain
        cam.setFrustumPerspective(45.0f, (float)width / (float)height, 1, 1000);
        Vector3f loc = new Vector3f(500f, 150f, 500f);
        Vector3f left = new Vector3f(-1.0f, 0.0f, 0.0f);
        Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f);
        Vector3f dir = new Vector3f(0.0f, 0.0f, -1.0f);
       
        cam.setFrame(loc, left, up, dir);
       
        cam.update();
       
        display.getRenderer().setCamera(cam);
        //End camera movements
       
        //Timer for FPS
        timer = Timer.getTimer();
        //End Timer
       
        KeyBindingManager.getKeyBindingManager().set("exit", KeyInput.KEY_ESCAPE);
    }

    @Override
    protected void initGame()
    {
        scene = new Node("Root");
        Vector3f terrainScale = new Vector3f(1f,1f,1f);
       
        RawHeightMap myMap = new RawHeightMap("1.raw", 513, RawHeightMap.FORMAT_16BITLE, false);
        myMap.setHeightScale(.001f);
        TerrainPage page = new TerrainPage("1", 33, myMap.getSize(), terrainScale, myMap.getHeightMap(), false);
       
        page.setLocalTranslation(new Vector3f(0f,0f,0f));
        page.setDetailTexture(1, 1);
        scene.attachChild(page);
        scene.updateGeometricState(0.0f, true);
        scene.updateRenderState();
    }

    @Override
    protected void reinit()
    {
        display.recreateWindow(width, height, depth, freq, fullscreen);
    }

    @Override
    protected void cleanup()
    {
       
    }
   
}



And I get this display

Unfortunately, I can't seem to move my camera to see if I have done it properly (although by the looks of that, probably not).

How do I make my camera move :P. If I need to, I'll set up my ThirdPersonController now. I just want to make sure I've done the heightmap right. Does that source code seem to be appropriate?

First thing I would recommend is using SimpleGame as a pose to BaseGame during the early stages of development.

SimpleGame has the camera, first person controls, lighting and various other niceties already setup for you :).





your code would then be like this



package fyrestoneclient;

import com.jme.app.SimpleGame;
import com.jme.math.Vector3f;
import com.jmex.terrain.TerrainPage;
import com.jmex.terrain.util.RawHeightMap;


public class FyrestoneClient extends SimpleGame {

    public static void main( String args[] ) {
        FyrestoneClient app = new FyrestoneClient();
        app.setDialogBehaviour( AbstractGame.FIRSTRUN_OR_NOCONFIGFILE_SHOW_PROPS_DIALOG );
        app.start();
    }
   

    @Override
    protected void simpleInitGame() {

        Vector3f terrainScale = new Vector3f( 10f, 1f, 10f );

        RawHeightMap myMap = new RawHeightMap( "1.raw", 513, RawHeightMap.FORMAT_16BITLE, false );
        myMap.setHeightScale( .001f );
        TerrainPage page = new TerrainPage( "1", 33, myMap.getSize(), terrainScale, myMap.getHeightMap(), false );

        page.setLocalTranslation( new Vector3f( 0f, 0f, 0f ) );
        page.setDetailTexture( 1, 1 );
        rootNode.attachChild( page );
        rootNode.updateGeometricState( 0.0f, true );
        rootNode.updateRenderState();
    }
}



Now, that should work. at least it does for me using numbers as a pose to the RAW file (and isn't it nice and slim??)

Also, notice the change in the terrain scale...

Why the change in Terrain Scale? Wouldn't it represent the heightmap exactly if I used 1, 1, 1?

My new code:


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package mappingexperiment2;

import com.jme.app.SimpleGame;
import com.jme.math.Vector3f;
import com.jmex.terrain.TerrainPage;
import com.jmex.terrain.util.RawHeightMap;

/**
 *
 * @author Tyler
 */
public class MapLoaderTest extends SimpleGame
{
    public static void main(String args[])
    {
        MapLoaderTest app = new MapLoaderTest();
        app.setDialogBehaviour(SimpleGame.FIRSTRUN_OR_NOCONFIGFILE_SHOW_PROPS_DIALOG);
        app.start();
    }

    protected void simpleInitGame()
    {
        Vector3f terrainScale = new Vector3f(10f, 1f, 10f);
        RawHeightMap myMap = new RawHeightMap("height.raw", 513, RawHeightMap.FORMAT_16BITLE, false);
        myMap.setHeightScale(1f);
        TerrainPage page = new TerrainPage("1", 33, myMap.getSize(), terrainScale, myMap.getHeightMap(), false);
        page.setLocalTranslation(new Vector3f(0f,0f,0f));
        page.setDetailTexture(1, 16);
        rootNode.attachChild(page);
        rootNode.updateGeometricState(0.0f, true);
        rootNode.updateRenderState();
    }
}



Now I'm standing in a gigantic valley, presumably somewhere on my heightmap. What do I do to make it look like it should :(

You also should add:


page.setModelBound(new BoundingBox());
page.updateModelBound();


to the code in simpleInitGame().  This initializes the bounding volume for the page for culling purposes.  I forgot to do this in my terrain and had the terrain disappearing not far from the camera as the bounding volume must have defaulted to 1. 
Anubis said:

You also should add:


page.setModelBound(new BoundingBox());
page.updateModelBound();


to the code in simpleInitGame().  This initializes the bounding volume for the page for culling purposes.  I forgot to do this in my terrain and had the terrain disappearing not far from the camera as the bounding volume must have defaulted to 1. 


True, me leaving this out was causing culling errors. The errors regarding the heightmap being HUGE and me standing in an immense valley (which is really just a shallow river) was resolved by me changing the TerrainScale to 5, .003f, 6 which were values in TestIsland. Does anybody have ANY idea how to figure out ideal values for the TerrainScale?

Also, I'd like to start applying Splat Textures. I have two textures, a base and a detail, and I have an alpha for the latter. How would I go about texturing the land?

Check out jmetest.TutorialGuide.HelloTerrain.  It explains how to use ProceduralTextureGenerator which allows you to combine 1 or more textures based on the height of the terrain at any given point. The heights must be given in height map coordinates, but this can be easily converted given the scale.  I've found a lot of useful stuff in the jmetest.TutorialGuide section and the rest of the jmetest packages.  The procedurally generated texture can then be combined with a detail texture and both added to a TextureState that can be applied to the TerrainPage.  Calling TerrainPage.setDetailTexture with the texture unit number of the detail texture set in the texture state will then apply the detail texture to the terrain.

I don't want procedural generation. I want WYSIWYG, which can be attained using splat textures. I've SORTA figured that out… ish…

What did you end up using for texture splatting? 

Make your app extend SimplePassGame and use the examples found in TestIsland and TestTerrainSplatting under [YOUR PATH TO JME]srcjmetestterrain.



You'll probably find TestTerrainSplatting more useful, as there is no other trash-code (i.e. the Water in TestIsland) that convolutes what you're looking for.