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.bullet;
33
34import com.jme3.animation.AnimChannel;
35import com.jme3.animation.AnimControl;
36import com.jme3.animation.AnimEventListener;
37import com.jme3.animation.LoopMode;
38import com.jme3.app.SimpleApplication;
39import com.jme3.bullet.BulletAppState;
40import com.jme3.bullet.PhysicsSpace;
41import com.jme3.bullet.collision.PhysicsCollisionEvent;
42import com.jme3.bullet.collision.PhysicsCollisionListener;
43import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
44import com.jme3.bullet.collision.shapes.SphereCollisionShape;
45import com.jme3.bullet.control.CharacterControl;
46import com.jme3.bullet.control.RigidBodyControl;
47import com.jme3.bullet.util.CollisionShapeFactory;
48import com.jme3.effect.ParticleEmitter;
49import com.jme3.effect.ParticleMesh.Type;
50import com.jme3.effect.shapes.EmitterSphereShape;
51import com.jme3.input.ChaseCamera;
52import com.jme3.input.KeyInput;
53import com.jme3.input.controls.ActionListener;
54import com.jme3.input.controls.KeyTrigger;
55import com.jme3.light.DirectionalLight;
56import com.jme3.material.Material;
57import com.jme3.math.ColorRGBA;
58import com.jme3.math.Vector2f;
59import com.jme3.math.Vector3f;
60import com.jme3.post.FilterPostProcessor;
61import com.jme3.post.filters.BloomFilter;
62import com.jme3.renderer.Camera;
63import com.jme3.renderer.queue.RenderQueue.ShadowMode;
64import com.jme3.scene.Geometry;
65import com.jme3.scene.Node;
66import com.jme3.scene.Spatial;
67import com.jme3.scene.shape.Box;
68import com.jme3.scene.shape.Sphere;
69import com.jme3.scene.shape.Sphere.TextureMode;
70import com.jme3.terrain.geomipmap.TerrainLodControl;
71import com.jme3.terrain.geomipmap.TerrainQuad;
72import com.jme3.terrain.heightmap.AbstractHeightMap;
73import com.jme3.terrain.heightmap.ImageBasedHeightMap;
74import com.jme3.texture.Texture;
75import com.jme3.texture.Texture.WrapMode;
76import com.jme3.util.SkyFactory;
77import java.util.ArrayList;
78import java.util.List;
79
80/**
81 * A walking animated character followed by a 3rd person camera on a terrain with LOD.
82 * @author normenhansen
83 */
84public class TestWalkingChar extends SimpleApplication implements ActionListener, PhysicsCollisionListener, AnimEventListener {
85
86    private BulletAppState bulletAppState;
87    //character
88    CharacterControl character;
89    Node model;
90    //temp vectors
91    Vector3f walkDirection = new Vector3f();
92    //terrain
93    TerrainQuad terrain;
94    RigidBodyControl terrainPhysicsNode;
95    //Materials
96    Material matRock;
97    Material matBullet;
98    //animation
99    AnimChannel animationChannel;
100    AnimChannel shootingChannel;
101    AnimControl animationControl;
102    float airTime = 0;
103    //camera
104    boolean left = false, right = false, up = false, down = false;
105    ChaseCamera chaseCam;
106    //bullet
107    Sphere bullet;
108    SphereCollisionShape bulletCollisionShape;
109    //explosion
110    ParticleEmitter effect;
111    //brick wall
112    Box brick;
113    float bLength = 0.8f;
114    float bWidth = 0.4f;
115    float bHeight = 0.4f;
116    FilterPostProcessor fpp;
117
118    public static void main(String[] args) {
119        TestWalkingChar app = new TestWalkingChar();
120        app.start();
121    }
122
123    @Override
124    public void simpleInitApp() {
125        bulletAppState = new BulletAppState();
126        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
127        stateManager.attach(bulletAppState);
128        setupKeys();
129        prepareBullet();
130        prepareEffect();
131        createLight();
132        createSky();
133        createTerrain();
134        createWall();
135        createCharacter();
136        setupChaseCamera();
137        setupAnimationController();
138        setupFilter();
139    }
140
141    private void setupFilter() {
142        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
143        BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects);
144        fpp.addFilter(bloom);
145        viewPort.addProcessor(fpp);
146    }
147
148    private PhysicsSpace getPhysicsSpace() {
149        return bulletAppState.getPhysicsSpace();
150    }
151
152    private void setupKeys() {
153        inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));
154        inputManager.addListener(this, "wireframe");
155        inputManager.addMapping("CharLeft", new KeyTrigger(KeyInput.KEY_A));
156        inputManager.addMapping("CharRight", new KeyTrigger(KeyInput.KEY_D));
157        inputManager.addMapping("CharUp", new KeyTrigger(KeyInput.KEY_W));
158        inputManager.addMapping("CharDown", new KeyTrigger(KeyInput.KEY_S));
159        inputManager.addMapping("CharSpace", new KeyTrigger(KeyInput.KEY_RETURN));
160        inputManager.addMapping("CharShoot", new KeyTrigger(KeyInput.KEY_SPACE));
161        inputManager.addListener(this, "CharLeft");
162        inputManager.addListener(this, "CharRight");
163        inputManager.addListener(this, "CharUp");
164        inputManager.addListener(this, "CharDown");
165        inputManager.addListener(this, "CharSpace");
166        inputManager.addListener(this, "CharShoot");
167    }
168
169    private void createWall() {
170        float xOff = -144;
171        float zOff = -40;
172        float startpt = bLength / 4 - xOff;
173        float height = 6.1f;
174        brick = new Box(Vector3f.ZERO, bLength, bHeight, bWidth);
175        brick.scaleTextureCoordinates(new Vector2f(1f, .5f));
176        for (int j = 0; j < 15; j++) {
177            for (int i = 0; i < 4; i++) {
178                Vector3f vt = new Vector3f(i * bLength * 2 + startpt, bHeight + height, zOff);
179                addBrick(vt);
180            }
181            startpt = -startpt;
182            height += 1.01f * bHeight;
183        }
184    }
185
186    private void addBrick(Vector3f ori) {
187        Geometry reBoxg = new Geometry("brick", brick);
188        reBoxg.setMaterial(matBullet);
189        reBoxg.setLocalTranslation(ori);
190        reBoxg.addControl(new RigidBodyControl(1.5f));
191        reBoxg.setShadowMode(ShadowMode.CastAndReceive);
192        this.rootNode.attachChild(reBoxg);
193        this.getPhysicsSpace().add(reBoxg);
194    }
195
196    private void prepareBullet() {
197        bullet = new Sphere(32, 32, 0.4f, true, false);
198        bullet.setTextureMode(TextureMode.Projected);
199        bulletCollisionShape = new SphereCollisionShape(0.4f);
200        matBullet = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
201        matBullet.setColor("Color", ColorRGBA.Green);
202        matBullet.setColor("m_GlowColor", ColorRGBA.Green);
203        getPhysicsSpace().addCollisionListener(this);
204    }
205
206    private void prepareEffect() {
207        int COUNT_FACTOR = 1;
208        float COUNT_FACTOR_F = 1f;
209        effect = new ParticleEmitter("Flame", Type.Triangle, 32 * COUNT_FACTOR);
210        effect.setSelectRandomImage(true);
211        effect.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (float) (1f / COUNT_FACTOR_F)));
212        effect.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f));
213        effect.setStartSize(1.3f);
214        effect.setEndSize(2f);
215        effect.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
216        effect.setParticlesPerSec(0);
217        effect.setGravity(0, -5, 0);
218        effect.setLowLife(.4f);
219        effect.setHighLife(.5f);
220        effect.setInitialVelocity(new Vector3f(0, 7, 0));
221        effect.setVelocityVariation(1f);
222        effect.setImagesX(2);
223        effect.setImagesY(2);
224        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
225        mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
226        effect.setMaterial(mat);
227//        effect.setLocalScale(100);
228        rootNode.attachChild(effect);
229    }
230
231    private void createLight() {
232        Vector3f direction = new Vector3f(-0.1f, -0.7f, -1).normalizeLocal();
233        DirectionalLight dl = new DirectionalLight();
234        dl.setDirection(direction);
235        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
236        rootNode.addLight(dl);
237    }
238
239    private void createSky() {
240        rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false));
241    }
242
243    private void createTerrain() {
244        matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
245        matRock.setBoolean("useTriPlanarMapping", false);
246        matRock.setBoolean("WardIso", true);
247        matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
248        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
249        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
250        grass.setWrap(WrapMode.Repeat);
251        matRock.setTexture("DiffuseMap", grass);
252        matRock.setFloat("DiffuseMap_0_scale", 64);
253        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
254        dirt.setWrap(WrapMode.Repeat);
255        matRock.setTexture("DiffuseMap_1", dirt);
256        matRock.setFloat("DiffuseMap_1_scale", 16);
257        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
258        rock.setWrap(WrapMode.Repeat);
259        matRock.setTexture("DiffuseMap_2", rock);
260        matRock.setFloat("DiffuseMap_2_scale", 128);
261        Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
262        normalMap0.setWrap(WrapMode.Repeat);
263        Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
264        normalMap1.setWrap(WrapMode.Repeat);
265        Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
266        normalMap2.setWrap(WrapMode.Repeat);
267        matRock.setTexture("NormalMap", normalMap0);
268        matRock.setTexture("NormalMap_1", normalMap2);
269        matRock.setTexture("NormalMap_2", normalMap2);
270
271        AbstractHeightMap heightmap = null;
272        try {
273            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f);
274            heightmap.load();
275
276        } catch (Exception e) {
277            e.printStackTrace();
278        }
279
280        terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());
281        List<Camera> cameras = new ArrayList<Camera>();
282        cameras.add(getCamera());
283        TerrainLodControl control = new TerrainLodControl(terrain, cameras);
284        terrain.addControl(control);
285        terrain.setMaterial(matRock);
286        terrain.setLocalScale(new Vector3f(2, 2, 2));
287
288        terrainPhysicsNode = new RigidBodyControl(CollisionShapeFactory.createMeshShape(terrain), 0);
289        terrain.addControl(terrainPhysicsNode);
290        rootNode.attachChild(terrain);
291        getPhysicsSpace().add(terrainPhysicsNode);
292    }
293
294    private void createCharacter() {
295        CapsuleCollisionShape capsule = new CapsuleCollisionShape(3f, 4f);
296        character = new CharacterControl(capsule, 0.01f);
297        model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
298        //model.setLocalScale(0.5f);
299        model.addControl(character);
300        character.setPhysicsLocation(new Vector3f(-140, 15, -10));
301        rootNode.attachChild(model);
302        getPhysicsSpace().add(character);
303    }
304
305    private void setupChaseCamera() {
306        flyCam.setEnabled(false);
307        chaseCam = new ChaseCamera(cam, model, inputManager);
308    }
309
310    private void setupAnimationController() {
311        animationControl = model.getControl(AnimControl.class);
312        animationControl.addListener(this);
313        animationChannel = animationControl.createChannel();
314        shootingChannel = animationControl.createChannel();
315        shootingChannel.addBone(animationControl.getSkeleton().getBone("uparm.right"));
316        shootingChannel.addBone(animationControl.getSkeleton().getBone("arm.right"));
317        shootingChannel.addBone(animationControl.getSkeleton().getBone("hand.right"));
318    }
319
320    @Override
321    public void simpleUpdate(float tpf) {
322        Vector3f camDir = cam.getDirection().clone().multLocal(0.1f);
323        Vector3f camLeft = cam.getLeft().clone().multLocal(0.1f);
324        camDir.y = 0;
325        camLeft.y = 0;
326        walkDirection.set(0, 0, 0);
327        if (left) {
328            walkDirection.addLocal(camLeft);
329        }
330        if (right) {
331            walkDirection.addLocal(camLeft.negate());
332        }
333        if (up) {
334            walkDirection.addLocal(camDir);
335        }
336        if (down) {
337            walkDirection.addLocal(camDir.negate());
338        }
339        if (!character.onGround()) {
340            airTime = airTime + tpf;
341        } else {
342            airTime = 0;
343        }
344        if (walkDirection.length() == 0) {
345            if (!"stand".equals(animationChannel.getAnimationName())) {
346                animationChannel.setAnim("stand", 1f);
347            }
348        } else {
349            character.setViewDirection(walkDirection);
350            if (airTime > .3f) {
351                if (!"stand".equals(animationChannel.getAnimationName())) {
352                    animationChannel.setAnim("stand");
353                }
354            } else if (!"Walk".equals(animationChannel.getAnimationName())) {
355                animationChannel.setAnim("Walk", 0.7f);
356            }
357        }
358        character.setWalkDirection(walkDirection);
359    }
360
361    public void onAction(String binding, boolean value, float tpf) {
362        if (binding.equals("CharLeft")) {
363            if (value) {
364                left = true;
365            } else {
366                left = false;
367            }
368        } else if (binding.equals("CharRight")) {
369            if (value) {
370                right = true;
371            } else {
372                right = false;
373            }
374        } else if (binding.equals("CharUp")) {
375            if (value) {
376                up = true;
377            } else {
378                up = false;
379            }
380        } else if (binding.equals("CharDown")) {
381            if (value) {
382                down = true;
383            } else {
384                down = false;
385            }
386        } else if (binding.equals("CharSpace")) {
387            character.jump();
388        } else if (binding.equals("CharShoot") && !value) {
389            bulletControl();
390        }
391    }
392
393    private void bulletControl() {
394        shootingChannel.setAnim("Dodge", 0.1f);
395        shootingChannel.setLoopMode(LoopMode.DontLoop);
396        Geometry bulletg = new Geometry("bullet", bullet);
397        bulletg.setMaterial(matBullet);
398        bulletg.setShadowMode(ShadowMode.CastAndReceive);
399        bulletg.setLocalTranslation(character.getPhysicsLocation().add(cam.getDirection().mult(5)));
400        RigidBodyControl bulletControl = new BombControl(bulletCollisionShape, 1);
401        bulletControl.setCcdMotionThreshold(0.1f);
402        bulletControl.setLinearVelocity(cam.getDirection().mult(80));
403        bulletg.addControl(bulletControl);
404        rootNode.attachChild(bulletg);
405        getPhysicsSpace().add(bulletControl);
406    }
407
408    public void collision(PhysicsCollisionEvent event) {
409        if (event.getObjectA() instanceof BombControl) {
410            final Spatial node = event.getNodeA();
411            effect.killAllParticles();
412            effect.setLocalTranslation(node.getLocalTranslation());
413            effect.emitAllParticles();
414        } else if (event.getObjectB() instanceof BombControl) {
415            final Spatial node = event.getNodeB();
416            effect.killAllParticles();
417            effect.setLocalTranslation(node.getLocalTranslation());
418            effect.emitAllParticles();
419        }
420    }
421
422    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
423        if (channel == shootingChannel) {
424            channel.setAnim("stand");
425        }
426    }
427
428    public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
429    }
430}
431