### Terrain Collision

*Work in progress*

This page will extend (not the Java keyword!) the Hello_Terrain tutorial in a few small ways in order to show you a quick way to create a Collision Shape out of the map we have generated. A Collision Shape allows a player (who is also a Collision Shape in this tutorial) to collide with the map, i.e. clip, walk on, stand on, etc. in order to create the type of world and interactivity most gamers are familiar with in FPS or MMOs.

about

### Sample Code

    package mygame;

    import jme3tools.converters.ImageToAwt;
    import com.jme3.app.SimpleBulletApplication;
    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.input.KeyInput;
    import com.jme3.input.controls.ActionListener;
    import com.jme3.input.controls.KeyTrigger;
    import com.jme3.material.Material;
    import com.jme3.math.Vector3f;
    import com.jme3.renderer.Camera;
    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 SimpleBulletApplication {

        private TerrainQuad terrain;
        PhysicsCharacterNode player;
        Boolean left = false, right = false, up = false, down = false;
        private Vector3f walkDirection = new Vector3f();

        public static void main(String[] args) {
            Main app = new Main();
            app.start();
        }

        @Override
        public void simpleInitApp() {
            setupKeys();
            setupPlayer();

            Material terrain_mat;
            PhysicsNode landscape;

            /** 1. Create terrain material and load four textures into it. */
            terrain_mat = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");

            /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */
            terrain_mat.setTexture("m_Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));

            /** 1.2) Add GRASS texture into the red layer (m_Tex1). */
            Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
            grass.setWrap(WrapMode.Repeat);
            terrain_mat.setTexture("m_Tex1", grass);
            terrain_mat.setFloat("m_Tex1Scale", 64f);

            /** 1.3) Add DIRT texture into the green layer (m_Tex2) */
            Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
            dirt.setWrap(WrapMode.Repeat);
            terrain_mat.setTexture("m_Tex2", dirt);
            terrain_mat.setFloat("m_Tex2Scale", 32f);

            /** 1.4) Add ROAD texture into the blue layer (m_Tex3) */
            Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
            rock.setWrap(WrapMode.Repeat);
            terrain_mat.setTexture("m_Tex3", rock);
            terrain_mat.setFloat("m_Tex3Scale", 128f);

            /** 2. Create the height map */
            AbstractHeightMap heightmap = null;
            Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
            heightmap = new ImageBasedHeightMap(
                    ImageToAwt.convert(heightMapImage.getImage(), false, true, 0));
            heightmap.load();

            /** 3. We have prepared material and heightmap. Now we create the actual terrain:
             * 3.1) We create a TerrainQuad and name it "my terrain".
             * 3.2) A good value for terrain tiles is 64x64 -- so we supply 64+1=65.
             * 3.3) We prepared a heightmap of size 512x512 -- so we supply 512+1=513.
             * 3.4) At last, we supply the prepared heightmap itself.*/
            terrain = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap());

            HeightfieldCollisionShape sceneShape = new HeightfieldCollisionShape(heightmap.getHeightMap());
            landscape = new PhysicsNode(terrain, sceneShape, 0);

            /** 4. The LOD (level of detail) depends on were the camera is: */
            List<Camera> cameras = new ArrayList<Camera>();
            cameras.add(getCamera());
            TerrainLodControl control = new TerrainLodControl(terrain, cameras);
            terrain.addControl(control);

            /** 5. We give the terrain its material, position & scale it, and attach it. */
            terrain.setMaterial(terrain_mat);
            landscape.setLocalTranslation(0, -100, 0);
            rootNode.attachChild(landscape);
            getPhysicsSpace().add(landscape);
        }

        @Override
        public void simpleUpdate(float tpf) {
            Vector3f camDir = cam.getDirection().clone().multLocal(0.2f);
            Vector3f camLeft = cam.getLeft().clone().multLocal(0.1f);
            walkDirection.set(0, 0, 0);
            if (left) {
                walkDirection.addLocal(camLeft);
            }
            if (right) {
                walkDirection.addLocal(camLeft.negate());
            }
            if (up) {
                walkDirection.addLocal(camDir);
                walkDirection.y = 0;
            }
            if (down) {
                walkDirection.addLocal(camDir.negate());
                walkDirection.y = 0;
            }
            player.setWalkDirection(walkDirection);
            cam.setLocation(player.getLocalTranslation());
        }

        public void setupPlayer() {
            player = new PhysicsCharacterNode(new CapsuleCollisionShape(1.2f, 3f, 1), .05f);
            player.setJumpSpeed(20);    //10 default
            player.setFallSpeed(30);    //30 default
            player.setGravity(30);      //30 default
            player.setLocalTranslation(new Vector3f(0f, 250f, 150f));
            rootNode.attachChild(player);
            getPhysicsSpace().add(player);
        }

        private void setupKeys() {
            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(actionListener, new String[]{"Lefts", "Rights", "Ups",
                        "Downs", "Jumps"});
        }
        private ActionListener() {

            public void onAction(String binding, boolean value, float tpf) {
                if (binding.equals("Lefts")) {
                    if (value && player.onGround()) {
                        left = true;
                    } else {
                        left = false;
                    }
                } else if (binding.equals("Rights")) {
                    if (value && player.onGround()) {
                        right = true;
                    } else {
                        right = false;
                    }
                } else if (binding.equals("Ups")) {
                    if (value && player.onGround()) {
                        up = true;
                    } else {
                        up = false;
                    }
                } else if (binding.equals("Downs")) {
                    if (value && player.onGround()) {
                        down = true;
                    } else {
                        down = false;
                    }
                } else if (binding.equals("Jumps") && player.onGround()) {
                    player.jump();
                }
            }
        };
    }

### What's Going On?

To try this code, go into a New Project → JME3 → BasicGame using the default settings (if you haven't already made a default project before) the name should be BasicGame and the package would be called myGame, then just paste this over the entire preconstructed main.java text. Then Add the jme3-test-data library which is available through your library list. This should compile and run from there.

### Explaining the Code

I will only briefly describe the sections that are covered in the original Hello_Terrain. Most detail will be given to the additives.

Imports are to bring in the appropriate class definitions.

    private TerrainQuad terrain;

Holds our map.

    PhysicsCharacterNode player;

Creates our character in the style of FPS that many gamers are familiar with.

    Boolean left = false, right = false, up = false, down = false;

Variables for creating fluid movement.

    private Vector3f walkDirection = new Vector3f();

The direction we determine from combinations of the booleans.

The main function is standard. In simpleInit we run setupKeys() first and create the common WASD and SPACEBAR to jump.

    inputManager.addListener(actionListener, new String[]{"Lefts", "Rights", "Ups", "Downs", "Jumps"})

This line indicates our actionListener function and what to do when each command (the string names) is sent.

The onAction function basically sets the booleans true based on which directions we're pressing or makes us jump. The simpleUpdate function constants changed our direction based on the boolean (ie the directions) we're pressing.

The real collisioning happens in these parts;

    player = new PhysicsCharacterNode(new CapsuleCollisionShape(1.2f, 3f, 1), .05f);
    rootNode.attachChild(player);
    getPhysicsSpace().add(player);
    HeightfieldCollisionShape sceneShape = new HeightfieldCollisionShape(heightmap.getHeightMap());
    landscape = new PhysicsNode(terrain, sceneShape, 0);
    landscape.setLocalTranslation(0, -100, 0);
    rootNode.attachChild(landscape);
    getPhysicsSpace().add(landscape);

The first chunk makes our player into a collision shape that's a capsule, kinda like a human right? And then attaches it to the rootNode and gets it's physics (important!). The second chunk feeds the heightmap we made into a heightfield collision shape generator to create the collidable shape of the land mass. The third chunk sets our land and it's collision down some, attaches em to root, and gets the physics.

### Conclusion

You should spawn high up in the area and fall down to the map, giving you a few seconds to survey the domain. Then walk around and see how you like the lay of the land.

See also:

- Hello Terrain,
- Terrain

view online version