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.terrain; 33 34 import com.jme3.app.SimpleApplication; 35 import com.jme3.bounding.BoundingBox; 36 import com.jme3.font.BitmapText; 37 import com.jme3.input.KeyInput; 38 import com.jme3.input.controls.ActionListener; 39 import com.jme3.input.controls.KeyTrigger; 40 import com.jme3.light.DirectionalLight; 41 import com.jme3.light.PointLight; 42 import com.jme3.material.Material; 43 import com.jme3.math.ColorRGBA; 44 import com.jme3.math.Vector3f; 45 import com.jme3.scene.Geometry; 46 import com.jme3.scene.Spatial; 47 import com.jme3.terrain.geomipmap.TerrainLodControl; 48 import com.jme3.terrain.geomipmap.TerrainQuad; 49 import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; 50 import com.jme3.terrain.heightmap.AbstractHeightMap; 51 import com.jme3.terrain.heightmap.ImageBasedHeightMap; 52 import com.jme3.texture.Texture; 53 import com.jme3.texture.Texture.WrapMode; 54 import com.jme3.util.SkyFactory; 55 import com.jme3.scene.Node; 56 import com.jme3.scene.debug.Arrow; 57 58 /** 59 * Uses the terrain's lighting texture with normal maps and lights. 60 * 61 * @author bowens 62 */ 63 public class TerrainTestAdvanced extends SimpleApplication { 64 65 private TerrainQuad terrain; 66 Material matTerrain; 67 Material matWire; 68 boolean wireframe = false; 69 boolean triPlanar = false; 70 boolean wardiso = false; 71 boolean minnaert = false; 72 protected BitmapText hintText; 73 PointLight pl; 74 Geometry lightMdl; 75 private float grassScale = 64; 76 private float dirtScale = 16; 77 private float rockScale = 128; 78 main(String[] args)79 public static void main(String[] args) { 80 TerrainTestAdvanced app = new TerrainTestAdvanced(); 81 app.start(); 82 } 83 84 @Override initialize()85 public void initialize() { 86 super.initialize(); 87 88 loadHintText(); 89 } 90 91 @Override simpleInitApp()92 public void simpleInitApp() { 93 setupKeys(); 94 95 // First, we load up our textures and the heightmap texture for the terrain 96 97 // TERRAIN TEXTURE material 98 matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); 99 matTerrain.setBoolean("useTriPlanarMapping", false); 100 matTerrain.setFloat("Shininess", 0.0f); 101 102 // ALPHA map (for splat textures) 103 matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png")); 104 matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png")); 105 106 // HEIGHTMAP image (for the terrain heightmap) 107 Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); 108 109 // GRASS texture 110 Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); 111 grass.setWrap(WrapMode.Repeat); 112 //matTerrain.setTexture("DiffuseMap_1", grass); 113 //matTerrain.setFloat("DiffuseMap_1_scale", grassScale); 114 115 // DIRT texture 116 Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); 117 dirt.setWrap(WrapMode.Repeat); 118 matTerrain.setTexture("DiffuseMap", dirt); 119 matTerrain.setFloat("DiffuseMap_0_scale", dirtScale); 120 121 // ROCK texture 122 Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); 123 rock.setWrap(WrapMode.Repeat); 124 //matTerrain.setTexture("DiffuseMap_2", rock); 125 //matTerrain.setFloat("DiffuseMap_2_scale", rockScale); 126 127 // BRICK texture 128 Texture brick = assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"); 129 brick.setWrap(WrapMode.Repeat); 130 //matTerrain.setTexture("DiffuseMap_3", brick); 131 //matTerrain.setFloat("DiffuseMap_3_scale", rockScale); 132 133 // RIVER ROCK texture 134 Texture riverRock = assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg"); 135 riverRock.setWrap(WrapMode.Repeat); 136 //matTerrain.setTexture("DiffuseMap_4", riverRock); 137 //matTerrain.setFloat("DiffuseMap_4_scale", rockScale); 138 139 140 Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); 141 normalMap0.setWrap(WrapMode.Repeat); 142 Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); 143 normalMap1.setWrap(WrapMode.Repeat); 144 Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); 145 normalMap2.setWrap(WrapMode.Repeat); 146 matTerrain.setTexture("NormalMap", normalMap0); 147 //matTerrain.setTexture("NormalMap_1", normalMap2); 148 //matTerrain.setTexture("NormalMap_2", normalMap2); 149 //matTerrain.setTexture("NormalMap_4", normalMap2); 150 151 // WIREFRAME material 152 matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); 153 matWire.getAdditionalRenderState().setWireframe(true); 154 matWire.setColor("Color", ColorRGBA.Green); 155 156 //createSky(); 157 158 // CREATE HEIGHTMAP 159 AbstractHeightMap heightmap = null; 160 try { 161 heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.5f); 162 heightmap.load(); 163 heightmap.smooth(0.9f, 1); 164 165 } catch (Exception e) { 166 e.printStackTrace(); 167 } 168 169 /* 170 * Here we create the actual terrain. The tiles will be 65x65, and the total size of the 171 * terrain will be 513x513. It uses the heightmap we created to generate the height values. 172 */ 173 /** 174 * Optimal terrain patch size is 65 (64x64). 175 * The total size is up to you. At 1025 it ran fine for me (200+FPS), however at 176 * size=2049, it got really slow. But that is a jump from 2 million to 8 million triangles... 177 */ 178 terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations 179 TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); 180 control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier 181 terrain.addControl(control); 182 terrain.setMaterial(matTerrain); 183 terrain.setModelBound(new BoundingBox()); 184 terrain.updateModelBound(); 185 terrain.setLocalTranslation(0, -100, 0); 186 terrain.setLocalScale(1f, 1f, 1f); 187 rootNode.attachChild(terrain); 188 189 Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m"); 190 //terrain.generateDebugTangents(debugMat); 191 192 DirectionalLight light = new DirectionalLight(); 193 light.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalize()); 194 rootNode.addLight(light); 195 196 cam.setLocation(new Vector3f(0, 10, -10)); 197 cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y); 198 flyCam.setMoveSpeed(400); 199 200 rootNode.attachChild(createAxisMarker(20)); 201 } 202 loadHintText()203 public void loadHintText() { 204 hintText = new BitmapText(guiFont, false); 205 hintText.setSize(guiFont.getCharSet().getRenderedSize()); 206 hintText.setLocalTranslation(0, getCamera().getHeight(), 0); 207 hintText.setText("Hit T to switch to wireframe, P to switch to tri-planar texturing"); 208 guiNode.attachChild(hintText); 209 } 210 setupKeys()211 private void setupKeys() { 212 flyCam.setMoveSpeed(50); 213 inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T)); 214 inputManager.addListener(actionListener, "wireframe"); 215 inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P)); 216 inputManager.addListener(actionListener, "triPlanar"); 217 inputManager.addMapping("WardIso", new KeyTrigger(KeyInput.KEY_9)); 218 inputManager.addListener(actionListener, "WardIso"); 219 inputManager.addMapping("Minnaert", new KeyTrigger(KeyInput.KEY_0)); 220 inputManager.addListener(actionListener, "Minnaert"); 221 } 222 private ActionListener actionListener = new ActionListener() { 223 224 public void onAction(String name, boolean pressed, float tpf) { 225 if (name.equals("wireframe") && !pressed) { 226 wireframe = !wireframe; 227 if (!wireframe) { 228 terrain.setMaterial(matWire); 229 } else { 230 terrain.setMaterial(matTerrain); 231 } 232 } else if (name.equals("triPlanar") && !pressed) { 233 triPlanar = !triPlanar; 234 if (triPlanar) { 235 matTerrain.setBoolean("useTriPlanarMapping", true); 236 // planar textures don't use the mesh's texture coordinates but real world coordinates, 237 // so we need to convert these texture coordinate scales into real world scales so it looks 238 // the same when we switch to/from tr-planar mode 239 matTerrain.setFloat("DiffuseMap_0_scale", 1f / (float) (512f / grassScale)); 240 matTerrain.setFloat("DiffuseMap_1_scale", 1f / (float) (512f / dirtScale)); 241 matTerrain.setFloat("DiffuseMap_2_scale", 1f / (float) (512f / rockScale)); 242 matTerrain.setFloat("DiffuseMap_3_scale", 1f / (float) (512f / rockScale)); 243 matTerrain.setFloat("DiffuseMap_4_scale", 1f / (float) (512f / rockScale)); 244 } else { 245 matTerrain.setBoolean("useTriPlanarMapping", false); 246 matTerrain.setFloat("DiffuseMap_0_scale", grassScale); 247 matTerrain.setFloat("DiffuseMap_1_scale", dirtScale); 248 matTerrain.setFloat("DiffuseMap_2_scale", rockScale); 249 matTerrain.setFloat("DiffuseMap_3_scale", rockScale); 250 matTerrain.setFloat("DiffuseMap_4_scale", rockScale); 251 } 252 } 253 } 254 }; 255 createSky()256 private void createSky() { 257 Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg"); 258 Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg"); 259 Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg"); 260 Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg"); 261 Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg"); 262 Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg"); 263 264 Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down); 265 rootNode.attachChild(sky); 266 } 267 createAxisMarker(float arrowSize)268 protected Node createAxisMarker(float arrowSize) { 269 270 Material redMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); 271 redMat.getAdditionalRenderState().setWireframe(true); 272 redMat.setColor("Color", ColorRGBA.Red); 273 274 Material greenMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); 275 greenMat.getAdditionalRenderState().setWireframe(true); 276 greenMat.setColor("Color", ColorRGBA.Green); 277 278 Material blueMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); 279 blueMat.getAdditionalRenderState().setWireframe(true); 280 blueMat.setColor("Color", ColorRGBA.Blue); 281 282 Node axis = new Node(); 283 284 // create arrows 285 Geometry arrowX = new Geometry("arrowX", new Arrow(new Vector3f(arrowSize, 0, 0))); 286 arrowX.setMaterial(redMat); 287 Geometry arrowY = new Geometry("arrowY", new Arrow(new Vector3f(0, arrowSize, 0))); 288 arrowY.setMaterial(greenMat); 289 Geometry arrowZ = new Geometry("arrowZ", new Arrow(new Vector3f(0, 0, arrowSize))); 290 arrowZ.setMaterial(blueMat); 291 axis.attachChild(arrowX); 292 axis.attachChild(arrowY); 293 axis.attachChild(arrowZ); 294 295 //axis.setModelBound(new BoundingBox()); 296 return axis; 297 } 298 } 299