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 */
32package jme3test.terrain;
33
34import com.jme3.bullet.collision.shapes.SphereCollisionShape;
35import com.jme3.app.SimpleApplication;
36import com.jme3.bounding.BoundingBox;
37import com.jme3.bullet.BulletAppState;
38import com.jme3.bullet.collision.shapes.SphereCollisionShape;
39import com.jme3.bullet.control.RigidBodyControl;
40import com.jme3.collision.CollisionResult;
41import com.jme3.collision.CollisionResults;
42import com.jme3.font.BitmapText;
43import com.jme3.input.KeyInput;
44import com.jme3.input.MouseInput;
45import com.jme3.input.controls.ActionListener;
46import com.jme3.input.controls.KeyTrigger;
47import com.jme3.input.controls.MouseButtonTrigger;
48import com.jme3.light.DirectionalLight;
49import com.jme3.light.PointLight;
50import com.jme3.material.Material;
51import com.jme3.math.ColorRGBA;
52import com.jme3.math.Ray;
53import com.jme3.math.Vector2f;
54import com.jme3.math.Vector3f;
55import com.jme3.scene.Geometry;
56import com.jme3.scene.Node;
57import com.jme3.scene.shape.Box;
58import com.jme3.scene.shape.Sphere;
59import com.jme3.terrain.geomipmap.TerrainLodControl;
60import com.jme3.terrain.geomipmap.TerrainQuad;
61import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
62import com.jme3.terrain.heightmap.AbstractHeightMap;
63import com.jme3.terrain.heightmap.ImageBasedHeightMap;
64import com.jme3.texture.Texture;
65import com.jme3.texture.Texture.WrapMode;
66import jme3tools.converters.ImageToAwt;
67
68/**
69 * Creates a terrain object and a collision node to go with it. Then
70 * drops several balls from the sky that collide with the terrain
71 * and roll around.
72 * Left click to place a sphere on the ground where the crosshairs intersect the terrain.
73 * Hit keys 1 or 2 to raise/lower the terrain at that spot.
74 *
75 * @author Brent Owens
76 */
77public class TerrainTestCollision extends SimpleApplication {
78
79    TerrainQuad terrain;
80    Node terrainPhysicsNode;
81    Material matRock;
82    Material matWire;
83    boolean wireframe = false;
84    protected BitmapText hintText;
85    PointLight pl;
86    Geometry lightMdl;
87    Geometry collisionMarker;
88    private BulletAppState bulletAppState;
89    Geometry collisionSphere;
90    Geometry collisionBox;
91    Geometry selectedCollisionObject;
92
93    public static void main(String[] args) {
94        TerrainTestCollision app = new TerrainTestCollision();
95        app.start();
96    }
97
98    @Override
99    public void initialize() {
100        super.initialize();
101        loadHintText();
102        initCrossHairs();
103    }
104
105    @Override
106    public void simpleInitApp() {
107        bulletAppState = new BulletAppState();
108        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
109        stateManager.attach(bulletAppState);
110        setupKeys();
111        matRock = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
112        matRock.setTexture("Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
113        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
114        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
115        grass.setWrap(WrapMode.Repeat);
116        matRock.setTexture("Tex1", grass);
117        matRock.setFloat("Tex1Scale", 64f);
118        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
119        dirt.setWrap(WrapMode.Repeat);
120        matRock.setTexture("Tex2", dirt);
121        matRock.setFloat("Tex2Scale", 32f);
122        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
123        rock.setWrap(WrapMode.Repeat);
124        matRock.setTexture("Tex3", rock);
125        matRock.setFloat("Tex3Scale", 128f);
126        matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
127        matWire.getAdditionalRenderState().setWireframe(true);
128        matWire.setColor("Color", ColorRGBA.Green);
129        AbstractHeightMap heightmap = null;
130        try {
131            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f);
132            heightmap.load();
133
134        } catch (Exception e) {
135        }
136
137        terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());
138        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
139        control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
140        terrain.addControl(control);
141        terrain.setMaterial(matRock);
142        terrain.setLocalScale(new Vector3f(2, 2, 2));
143        terrain.setLocked(false); // unlock it so we can edit the height
144        rootNode.attachChild(terrain);
145
146
147        /**
148         * Create PhysicsRigidBodyControl for collision
149         */
150        terrain.addControl(new RigidBodyControl(0));
151        bulletAppState.getPhysicsSpace().addAll(terrain);
152
153
154        // Add 5 physics spheres to the world, with random sizes and positions
155        // let them drop from the sky
156        for (int i = 0; i < 5; i++) {
157            float r = (float) (8 * Math.random());
158            Geometry sphere = new Geometry("cannonball", new Sphere(10, 10, r));
159            sphere.setMaterial(matWire);
160            float x = (float) (20 * Math.random()) - 40; // random position
161            float y = (float) (20 * Math.random()) - 40; // random position
162            float z = (float) (20 * Math.random()) - 40; // random position
163            sphere.setLocalTranslation(new Vector3f(x, 100 + y, z));
164            sphere.addControl(new RigidBodyControl(new SphereCollisionShape(r), 2));
165            rootNode.attachChild(sphere);
166            bulletAppState.getPhysicsSpace().add(sphere);
167        }
168
169        collisionBox = new Geometry("collisionBox", new Box(2, 2, 2));
170        collisionBox.setModelBound(new BoundingBox());
171        collisionBox.setLocalTranslation(new Vector3f(20, 95, 30));
172        collisionBox.setMaterial(matWire);
173        rootNode.attachChild(collisionBox);
174        selectedCollisionObject = collisionBox;
175
176        DirectionalLight dl = new DirectionalLight();
177        dl.setDirection(new Vector3f(1, -0.5f, -0.1f).normalizeLocal());
178        dl.setColor(new ColorRGBA(0.50f, 0.40f, 0.50f, 1.0f));
179        rootNode.addLight(dl);
180
181        cam.setLocation(new Vector3f(0, 25, -10));
182        cam.lookAtDirection(new Vector3f(0, -1, 0).normalizeLocal(), Vector3f.UNIT_Y);
183    }
184
185    public void loadHintText() {
186        hintText = new BitmapText(guiFont, false);
187        hintText.setSize(guiFont.getCharSet().getRenderedSize());
188        hintText.setLocalTranslation(0, getCamera().getHeight(), 0);
189        //hintText.setText("Hit T to switch to wireframe");
190        hintText.setText("");
191        guiNode.attachChild(hintText);
192    }
193
194    protected void initCrossHairs() {
195        //guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
196        BitmapText ch = new BitmapText(guiFont, false);
197        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
198        ch.setText("+"); // crosshairs
199        ch.setLocalTranslation( // center
200                settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
201                settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
202        guiNode.attachChild(ch);
203    }
204
205    private void setupKeys() {
206        flyCam.setMoveSpeed(50);
207        inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));
208        inputManager.addListener(actionListener, "wireframe");
209        inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_H));
210        inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K));
211        inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_U));
212        inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_J));
213        inputManager.addMapping("Forwards", new KeyTrigger(KeyInput.KEY_Y));
214        inputManager.addMapping("Backs", new KeyTrigger(KeyInput.KEY_I));
215        inputManager.addListener(actionListener, "Lefts");
216        inputManager.addListener(actionListener, "Rights");
217        inputManager.addListener(actionListener, "Ups");
218        inputManager.addListener(actionListener, "Downs");
219        inputManager.addListener(actionListener, "Forwards");
220        inputManager.addListener(actionListener, "Backs");
221        inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
222        inputManager.addListener(actionListener, "shoot");
223        inputManager.addMapping("cameraDown", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
224        inputManager.addListener(actionListener, "cameraDown");
225    }
226
227    @Override
228    public void update() {
229        super.update();
230    }
231
232    private void createCollisionMarker() {
233        Sphere s = new Sphere(6, 6, 1);
234        collisionMarker = new Geometry("collisionMarker");
235        collisionMarker.setMesh(s);
236        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
237        mat.setColor("Color", ColorRGBA.Orange);
238        collisionMarker.setMaterial(mat);
239        rootNode.attachChild(collisionMarker);
240    }
241    private ActionListener actionListener = new ActionListener() {
242
243        public void onAction(String binding, boolean keyPressed, float tpf) {
244            if (binding.equals("wireframe") && !keyPressed) {
245                wireframe = !wireframe;
246                if (!wireframe) {
247                    terrain.setMaterial(matWire);
248                } else {
249                    terrain.setMaterial(matRock);
250                }
251            } else if (binding.equals("shoot") && !keyPressed) {
252
253                Vector3f origin = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.0f);
254                Vector3f direction = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.3f);
255                direction.subtractLocal(origin).normalizeLocal();
256
257
258                Ray ray = new Ray(origin, direction);
259                CollisionResults results = new CollisionResults();
260                int numCollisions = terrain.collideWith(ray, results);
261                if (numCollisions > 0) {
262                    CollisionResult hit = results.getClosestCollision();
263                    if (collisionMarker == null) {
264                        createCollisionMarker();
265                    }
266                    Vector2f loc = new Vector2f(hit.getContactPoint().x, hit.getContactPoint().z);
267                    float height = terrain.getHeight(loc);
268                    System.out.println("collide " + hit.getContactPoint() + ", height: " + height + ", distance: " + hit.getDistance());
269                    collisionMarker.setLocalTranslation(new Vector3f(hit.getContactPoint().x, height, hit.getContactPoint().z));
270                }
271            } else if (binding.equals("cameraDown") && !keyPressed) {
272                getCamera().lookAtDirection(new Vector3f(0, -1, 0), Vector3f.UNIT_Y);
273            } else if (binding.equals("Lefts") && !keyPressed) {
274                Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone();
275                selectedCollisionObject.move(-0.5f, 0, 0);
276                testCollision(oldLoc);
277            } else if (binding.equals("Rights") && !keyPressed) {
278                Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone();
279                selectedCollisionObject.move(0.5f, 0, 0);
280                testCollision(oldLoc);
281            } else if (binding.equals("Forwards") && !keyPressed) {
282                Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone();
283                selectedCollisionObject.move(0, 0, 0.5f);
284                testCollision(oldLoc);
285            } else if (binding.equals("Backs") && !keyPressed) {
286                Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone();
287                selectedCollisionObject.move(0, 0, -0.5f);
288                testCollision(oldLoc);
289            } else if (binding.equals("Ups") && !keyPressed) {
290                Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone();
291                selectedCollisionObject.move(0, 0.5f, 0);
292                testCollision(oldLoc);
293            } else if (binding.equals("Downs") && !keyPressed) {
294                Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone();
295                selectedCollisionObject.move(0, -0.5f, 0);
296                testCollision(oldLoc);
297            }
298
299        }
300    };
301
302    private void testCollision(Vector3f oldLoc) {
303        if (terrain.collideWith(selectedCollisionObject.getWorldBound(), new CollisionResults()) > 0) {
304            selectedCollisionObject.setLocalTranslation(oldLoc);
305        }
306    }
307}
308