123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- = terrain_collision
- :revnumber: 2.1
- :revdate: 2020/07/24
- :keywords: terrain, collision
- == Terrain Collision
- This tutorial expands the HelloTerrain tutorial and makes the terrain solid. You combine what you learned in xref:tutorials:beginner/hello_terrain.adoc[Hello Terrain] and xref:tutorials:beginner/hello_collision.adoc[Hello Collision] and add a CollisionShape to the terrain. The terrain's CollisionShape lets the first-person player (who is also a CollisionShape) collide with the terrain, i.e. walk on it and stand on it.
- == Sample Code
- [source,java]
- ----
- package jme3test.helloworld;
- import com.jme3.app.SimpleApplication;
- import com.jme3.bullet.BulletAppState;
- import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
- import com.jme3.bullet.collision.shapes.CollisionShape;
- import com.jme3.bullet.control.CharacterControl;
- import com.jme3.bullet.control.RigidBodyControl;
- import com.jme3.bullet.util.CollisionShapeFactory;
- 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.scene.Node;
- import com.jme3.terrain.geomipmap.TerrainLodControl;
- import com.jme3.terrain.heightmap.AbstractHeightMap;
- import com.jme3.terrain.geomipmap.TerrainQuad;
- import com.jme3.terrain.heightmap.ImageBasedHeightMap;
- import com.jme3.texture.Texture;
- import com.jme3.texture.Texture.WrapMode;
- import java.util.ArrayList;
- import java.util.List;
- import jme3tools.converters.ImageToAwt;
- /**
- * This demo shows a terrain with collision detection,
- * that you can walk around in with a first-person perspective.
- * This code combines HelloCollision and HelloTerrain.
- */
- public class HelloTerrainCollision extends SimpleApplication
- implements ActionListener {
- private BulletAppState bulletAppState;
- private RigidBodyControl landscape;
- private CharacterControl player;
- private Vector3f walkDirection = new Vector3f();
- private boolean left = false, right = false, up = false, down = false;
- private TerrainQuad terrain;
- private Material mat_terrain;
- public static void main(String[] args) {
- HelloTerrainCollision app = new HelloTerrainCollision();
- app.start();
- }
- @Override
- public void simpleInitApp() {
- /** Set up Physics */
- bulletAppState = new BulletAppState();
- stateManager.attach(bulletAppState);
- //Uncomment for debugging.
- //bulletAppState.setDebugEnabled(true);
- flyCam.setMoveSpeed(100);
- setUpKeys();
- /** 1. Create terrain material and load four textures into it. */
- mat_terrain = new Material(assetManager,
- "Common/MatDefs/Terrain/Terrain.j3md");
- /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */
- mat_terrain.setTexture("Alpha", assetManager.loadTexture(
- "Textures/Terrain/splat/alphamap.png"));
- /** 1.2) Add GRASS texture into the red layer (Tex1). */
- Texture grass = assetManager.loadTexture(
- "Textures/Terrain/splat/grass.jpg");
- grass.setWrap(WrapMode.Repeat);
- mat_terrain.setTexture("Tex1", grass);
- mat_terrain.setFloat("Tex1Scale", 64f);
- /** 1.3) Add DIRT texture into the green layer (Tex2) */
- Texture dirt = assetManager.loadTexture(
- "Textures/Terrain/splat/dirt.jpg");
- dirt.setWrap(WrapMode.Repeat);
- mat_terrain.setTexture("Tex2", dirt);
- mat_terrain.setFloat("Tex2Scale", 32f);
- /** 1.4) Add ROAD texture into the blue layer (Tex3) */
- Texture rock = assetManager.loadTexture(
- "Textures/Terrain/splat/road.jpg");
- rock.setWrap(WrapMode.Repeat);
- mat_terrain.setTexture("Tex3", rock);
- mat_terrain.setFloat("Tex3Scale", 128f);
- /** 2. Create the height map */
- AbstractHeightMap heightmap = null;
- Texture heightMapImage = assetManager.loadTexture(
- "Textures/Terrain/splat/mountains512.png");
- heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
- heightmap.load();
- /** 3. We have prepared material and heightmap.
- * Now we create the actual terrain:
- * 3.1) 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) As LOD step scale we supply Vector3f(1,1,1).
- * 3.5) We supply the prepared heightmap itself.
- */
- terrain = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap());
- /** 4. We give the terrain its material, position & scale it, and attach it. */
- terrain.setMaterial(mat_terrain);
- terrain.setLocalTranslation(0, -100, 0);
- terrain.setLocalScale(2f, 1f, 2f);
- rootNode.attachChild(terrain);
- /** 5. 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);
- /** 6. Add physics:
- * We set up collision detection for the scene by creating a static
- * RigidBodyControl with mass zero.
- */
- terrain.addControl(new RigidBodyControl(0));
- /**
- * We set up collision detection for the player by creating
- * a capsule collision shape and a CharacterControl.
- * The CharacterControl offers extra settings for
- * size, stepheight, jumping, falling, and gravity.
- * We also put the player in its starting position.
- */
- CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1);
- player = new CharacterControl(capsuleShape, 0.05f);
- player.setJumpSpeed(20);
- player.setFallSpeed(30);
- player.setPhysicsLocation(new Vector3f(-10, 10, 10));
- // We attach the scene and the player to the rootnode and the physics space,
- // to make them appear in the game world.
- bulletAppState.getPhysicsSpace().add(terrain);
- bulletAppState.getPhysicsSpace().add(player);
- // You can change the gravity of individual physics objects after they are
- // added to the PhysicsSpace.
- player.setGravity(new Vector3f(0,-30f,0));
- }
- /** We over-write some navigational key mappings here, so we can
- * add physics-controlled walking and jumping: */
- private void setUpKeys() {
- inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A));
- inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D));
- inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W));
- inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S));
- inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE));
- inputManager.addListener(this, "Left");
- inputManager.addListener(this, "Right");
- inputManager.addListener(this, "Up");
- inputManager.addListener(this, "Down");
- inputManager.addListener(this, "Jump");
- }
- /** These are our custom actions triggered by key presses.
- * We do not walk yet, we just keep track of the direction the user pressed. */
- public void onAction(String binding, boolean value, float tpf) {
- if (binding.equals("Left")) {
- if (value) { left = true; } else { left = false; }
- } else if (binding.equals("Right")) {
- if (value) { right = true; } else { right = false; }
- } else if (binding.equals("Up")) {
- if (value) { up = true; } else { up = false; }
- } else if (binding.equals("Down")) {
- if (value) { down = true; } else { down = false; }
- } else if (binding.equals("Jump")) {
- player.jump(new Vector3f(0,20f,0));
- }
- }
- /**
- * 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.
- */
- @Override
- 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()); }
- player.setWalkDirection(walkDirection);
- cam.setLocation(player.getPhysicsLocation());
- }
- }
- ----
- To try this code, create a `menu:New Project[JME3 > BasicGame]` using the default settings. Paste the sample code over the pregenerated Main.java class. Change the package to '`mygame`' if necessary. Open the `menu:File[Project Properties > Libraries]` and add the `jme3-test-data` library to make certain you have all the files.
- Compile and run the code. You should see a terrain. You can use the WASD keys and the mouse to run up and down the hills.
- == Understanding the Code
- === The Terrain Code
- Read xref:tutorials:beginner/hello_terrain.adoc[Hello Terrain] for details of the following parts that we reuse:
- . The `AbstractHeightMap` is an efficient way to describe the shape of the terrain.
- . The `Terrain.j3md`-based Material and its texture layers let you colorize rocky mountain, grassy valleys, and a paved path criss-crossing over the landscape.
- . The TerrainQuad is the finished `terrain` Spatial that you attach to the rootNode.
- === The Collision Detection Code
- Read xref:tutorials:beginner/hello_collision.adoc[Hello Collision] for details of the following parts that we reuse:
- . The `BulletAppState` lines activate physics.
- . The `ActionListener` (`onAction()`) lets you reconfigure the input handling for the first-person player, so it takes collision detection into account.
- . The custom `setUpKeys()` method loads your reconfigured input handlers. They now don't just walk blindly, but calculate the `walkDirection` vector that we need for collision detection.
- . `simpleUpdate()` uses the `walkDirection` vector and makes the character walk, while taking obstacles and solid walls/floor into account.
- [source,java]
- ----
- player.setWalkDirection(walkDirection);
- ----
- . The RigidBodyControl `landscape` is the CollisionShape of the terrain.
- . The physical first-person player is a CapsuleCollisionShape with a CharacterControl.
- === Combining the Two
- Here are the changed parts to combine the two:
- . You create a static (zero-mass) RigidBodyControl.
- . Add the control to the `terrain` to make it physical.
- [source,java]
- ----
- /** 6. Add physics: */
- terrain.addControl(new RigidBodyControl(0));
- ----
- You attach the `terrain` and the first-person `player` to the rootNode, and to the physics space, to make them appear in the game world.
- [source,java]
- ----
- bulletAppState.getPhysicsSpace().add(terrain);
- bulletAppState.getPhysicsSpace().add(player);
- ----
- == Conclusion
- You see that you can combine snippets of sample code (such as HelloTerrain and HelloCollision), and create a new application from it that combines two features into something new.
- You should spawn high up in the area and fall down to the map, giving you a few seconds to survey the area. Then walk around and see how you like the lay of the land.
- '''
- See also:
- * xref:tutorials:beginner/hello_terrain.adoc[Hello Terrain],
- * xref:terrain/terrain.adoc[Terrain]
|