1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package jme3test.helloworld;
34
35import com.jme3.app.SimpleApplication;
36import com.jme3.bullet.BulletAppState;
37import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
38import com.jme3.bullet.collision.shapes.CollisionShape;
39import com.jme3.bullet.control.CharacterControl;
40import com.jme3.bullet.control.RigidBodyControl;
41import com.jme3.bullet.util.CollisionShapeFactory;
42import com.jme3.input.KeyInput;
43import com.jme3.input.controls.ActionListener;
44import com.jme3.input.controls.KeyTrigger;
45import com.jme3.material.Material;
46import com.jme3.math.Vector3f;
47import com.jme3.renderer.Camera;
48import com.jme3.scene.Node;
49import com.jme3.terrain.geomipmap.TerrainLodControl;
50import com.jme3.terrain.geomipmap.TerrainQuad;
51import com.jme3.terrain.heightmap.AbstractHeightMap;
52import com.jme3.terrain.heightmap.ImageBasedHeightMap;
53import com.jme3.texture.Texture;
54import com.jme3.texture.Texture.WrapMode;
55import java.util.ArrayList;
56import java.util.List;
57
58/**
59 * This demo shows a terrain with collision detection,
60 * that you can walk around in with a first-person perspective.
61 * This code combines HelloCollision and HelloTerrain.
62 */
63public class HelloTerrainCollision extends SimpleApplication
64        implements ActionListener {
65
66  private BulletAppState bulletAppState;
67  private RigidBodyControl landscape;
68  private CharacterControl player;
69  private Vector3f walkDirection = new Vector3f();
70  private boolean left = false, right = false, up = false, down = false;
71  private TerrainQuad terrain;
72  private Material mat_terrain;
73
74  public static void main(String[] args) {
75    HelloTerrainCollision app = new HelloTerrainCollision();
76    app.start();
77  }
78
79  @Override
80  public void simpleInitApp() {
81    /** Set up Physics */
82    bulletAppState = new BulletAppState();
83    stateManager.attach(bulletAppState);
84    //bulletAppState.getPhysicsSpace().enableDebug(assetManager);
85
86    flyCam.setMoveSpeed(100);
87    setUpKeys();
88
89    /** 1. Create terrain material and load four textures into it. */
90    mat_terrain = new Material(assetManager,
91            "Common/MatDefs/Terrain/Terrain.j3md");
92
93    /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */
94    mat_terrain.setTexture("Alpha", assetManager.loadTexture(
95            "Textures/Terrain/splat/alphamap.png"));
96
97    /** 1.2) Add GRASS texture into the red layer (Tex1). */
98    Texture grass = assetManager.loadTexture(
99            "Textures/Terrain/splat/grass.jpg");
100    grass.setWrap(WrapMode.Repeat);
101    mat_terrain.setTexture("Tex1", grass);
102    mat_terrain.setFloat("Tex1Scale", 64f);
103
104    /** 1.3) Add DIRT texture into the green layer (Tex2) */
105    Texture dirt = assetManager.loadTexture(
106            "Textures/Terrain/splat/dirt.jpg");
107    dirt.setWrap(WrapMode.Repeat);
108    mat_terrain.setTexture("Tex2", dirt);
109    mat_terrain.setFloat("Tex2Scale", 32f);
110
111    /** 1.4) Add ROAD texture into the blue layer (Tex3) */
112    Texture rock = assetManager.loadTexture(
113            "Textures/Terrain/splat/road.jpg");
114    rock.setWrap(WrapMode.Repeat);
115    mat_terrain.setTexture("Tex3", rock);
116    mat_terrain.setFloat("Tex3Scale", 128f);
117
118    /** 2. Create the height map */
119    AbstractHeightMap heightmap = null;
120    Texture heightMapImage = assetManager.loadTexture(
121            "Textures/Terrain/splat/mountains512.png");
122    heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
123    heightmap.load();
124
125    /** 3. We have prepared material and heightmap.
126     * Now we create the actual terrain:
127     * 3.1) Create a TerrainQuad and name it "my terrain".
128     * 3.2) A good value for terrain tiles is 64x64 -- so we supply 64+1=65.
129     * 3.3) We prepared a heightmap of size 512x512 -- so we supply 512+1=513.
130     * 3.4) As LOD step scale we supply Vector3f(1,1,1).
131     * 3.5) We supply the prepared heightmap itself.
132     */
133    terrain = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap());
134
135    /** 4. We give the terrain its material, position & scale it, and attach it. */
136    terrain.setMaterial(mat_terrain);
137    terrain.setLocalTranslation(0, -100, 0);
138    terrain.setLocalScale(2f, 1f, 2f);
139    rootNode.attachChild(terrain);
140
141    /** 5. The LOD (level of detail) depends on were the camera is: */
142    List<Camera> cameras = new ArrayList<Camera>();
143    cameras.add(getCamera());
144    TerrainLodControl control = new TerrainLodControl(terrain, cameras);
145    terrain.addControl(control);
146
147    /** 6. Add physics: */
148    // We set up collision detection for the scene by creating a
149    // compound collision shape and a static RigidBodyControl with mass zero.*/
150    CollisionShape terrainShape =
151            CollisionShapeFactory.createMeshShape((Node) terrain);
152    landscape = new RigidBodyControl(terrainShape, 0);
153    terrain.addControl(landscape);
154
155    // We set up collision detection for the player by creating
156    // a capsule collision shape and a CharacterControl.
157    // The CharacterControl offers extra settings for
158    // size, stepheight, jumping, falling, and gravity.
159    // We also put the player in its starting position.
160    CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1);
161    player = new CharacterControl(capsuleShape, 0.05f);
162    player.setJumpSpeed(20);
163    player.setFallSpeed(30);
164    player.setGravity(30);
165    player.setPhysicsLocation(new Vector3f(0, 10, 0));
166
167    // We attach the scene and the player to the rootnode and the physics space,
168    // to make them appear in the game world.
169    bulletAppState.getPhysicsSpace().add(terrain);
170    bulletAppState.getPhysicsSpace().add(player);
171
172  }
173  /** We over-write some navigational key mappings here, so we can
174   * add physics-controlled walking and jumping: */
175  private void setUpKeys() {
176    inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A));
177    inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D));
178    inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W));
179    inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S));
180    inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE));
181    inputManager.addListener(this, "Left");
182    inputManager.addListener(this, "Right");
183    inputManager.addListener(this, "Up");
184    inputManager.addListener(this, "Down");
185    inputManager.addListener(this, "Jump");
186  }
187
188  /** These are our custom actions triggered by key presses.
189   * We do not walk yet, we just keep track of the direction the user pressed. */
190  public void onAction(String binding, boolean value, float tpf) {
191    if (binding.equals("Left")) {
192      if (value) { left = true; } else { left = false; }
193    } else if (binding.equals("Right")) {
194      if (value) { right = true; } else { right = false; }
195    } else if (binding.equals("Up")) {
196      if (value) { up = true; } else { up = false; }
197    } else if (binding.equals("Down")) {
198      if (value) { down = true; } else { down = false; }
199    } else if (binding.equals("Jump")) {
200      player.jump();
201    }
202  }
203
204  /**
205   * This is the main event loop--walking happens here.
206   * We check in which direction the player is walking by interpreting
207   * the camera direction forward (camDir) and to the side (camLeft).
208   * The setWalkDirection() command is what lets a physics-controlled player walk.
209   * We also make sure here that the camera moves with player.
210   */
211  @Override
212  public void simpleUpdate(float tpf) {
213    Vector3f camDir = cam.getDirection().clone().multLocal(0.6f);
214    Vector3f camLeft = cam.getLeft().clone().multLocal(0.4f);
215    walkDirection.set(0, 0, 0);
216    if (left)  { walkDirection.addLocal(camLeft); }
217    if (right) { walkDirection.addLocal(camLeft.negate()); }
218    if (up)    { walkDirection.addLocal(camDir); }
219    if (down)  { walkDirection.addLocal(camDir.negate()); }
220    player.setWalkDirection(walkDirection);
221    cam.setLocation(player.getPhysicsLocation());
222  }
223}
224
225