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 package jme3test.bullet; 33 34 import com.jme3.animation.AnimChannel; 35 import com.jme3.animation.AnimControl; 36 import com.jme3.animation.AnimEventListener; 37 import com.jme3.animation.LoopMode; 38 import com.jme3.app.SimpleApplication; 39 import com.jme3.bullet.BulletAppState; 40 import com.jme3.bullet.PhysicsSpace; 41 import com.jme3.bullet.collision.PhysicsCollisionEvent; 42 import com.jme3.bullet.collision.PhysicsCollisionListener; 43 import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; 44 import com.jme3.bullet.collision.shapes.SphereCollisionShape; 45 import com.jme3.bullet.control.CharacterControl; 46 import com.jme3.bullet.control.RigidBodyControl; 47 import com.jme3.bullet.util.CollisionShapeFactory; 48 import com.jme3.effect.ParticleEmitter; 49 import com.jme3.effect.ParticleMesh.Type; 50 import com.jme3.effect.shapes.EmitterSphereShape; 51 import com.jme3.input.ChaseCamera; 52 import com.jme3.input.KeyInput; 53 import com.jme3.input.controls.ActionListener; 54 import com.jme3.input.controls.KeyTrigger; 55 import com.jme3.light.DirectionalLight; 56 import com.jme3.material.Material; 57 import com.jme3.math.ColorRGBA; 58 import com.jme3.math.Vector2f; 59 import com.jme3.math.Vector3f; 60 import com.jme3.post.FilterPostProcessor; 61 import com.jme3.post.filters.BloomFilter; 62 import com.jme3.renderer.Camera; 63 import com.jme3.renderer.queue.RenderQueue.ShadowMode; 64 import com.jme3.scene.Geometry; 65 import com.jme3.scene.Node; 66 import com.jme3.scene.Spatial; 67 import com.jme3.scene.shape.Box; 68 import com.jme3.scene.shape.Sphere; 69 import com.jme3.scene.shape.Sphere.TextureMode; 70 import com.jme3.terrain.geomipmap.TerrainLodControl; 71 import com.jme3.terrain.geomipmap.TerrainQuad; 72 import com.jme3.terrain.heightmap.AbstractHeightMap; 73 import com.jme3.terrain.heightmap.ImageBasedHeightMap; 74 import com.jme3.texture.Texture; 75 import com.jme3.texture.Texture.WrapMode; 76 import com.jme3.util.SkyFactory; 77 import java.util.ArrayList; 78 import 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 */ 84 public 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