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.app.SimpleApplication; 35import com.jme3.collision.CollisionResult; 36import com.jme3.collision.CollisionResults; 37import com.jme3.font.BitmapText; 38import com.jme3.input.KeyInput; 39import com.jme3.input.MouseInput; 40import com.jme3.input.controls.ActionListener; 41import com.jme3.input.controls.KeyTrigger; 42import com.jme3.input.controls.MouseButtonTrigger; 43import com.jme3.light.AmbientLight; 44import com.jme3.light.DirectionalLight; 45import com.jme3.material.Material; 46import com.jme3.material.RenderState.BlendMode; 47import com.jme3.math.ColorRGBA; 48import com.jme3.math.Ray; 49import com.jme3.math.Vector2f; 50import com.jme3.math.Vector3f; 51import com.jme3.scene.Geometry; 52import com.jme3.scene.debug.Arrow; 53import com.jme3.scene.shape.Sphere; 54import com.jme3.terrain.geomipmap.TerrainGrid; 55import com.jme3.terrain.geomipmap.TerrainLodControl; 56import com.jme3.terrain.geomipmap.TerrainQuad; 57import com.jme3.terrain.geomipmap.grid.FractalTileLoader; 58import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; 59import com.jme3.terrain.heightmap.AbstractHeightMap; 60import com.jme3.terrain.heightmap.ImageBasedHeightMap; 61import com.jme3.terrain.noise.ShaderUtils; 62import com.jme3.terrain.noise.basis.FilteredBasis; 63import com.jme3.terrain.noise.filter.IterativeFilter; 64import com.jme3.terrain.noise.filter.OptimizedErode; 65import com.jme3.terrain.noise.filter.PerturbFilter; 66import com.jme3.terrain.noise.filter.SmoothFilter; 67import com.jme3.terrain.noise.fractal.FractalSum; 68import com.jme3.terrain.noise.modulator.NoiseModulator; 69import com.jme3.texture.Texture; 70import com.jme3.texture.Texture.WrapMode; 71import java.util.ArrayList; 72import java.util.List; 73 74/** 75 * 76 * @author Brent Owens 77 */ 78public class TerrainTestModifyHeight extends SimpleApplication { 79 80 private TerrainQuad terrain; 81 Material matTerrain; 82 Material matWire; 83 boolean wireframe = true; 84 boolean triPlanar = false; 85 boolean wardiso = false; 86 boolean minnaert = false; 87 protected BitmapText hintText; 88 private float grassScale = 64; 89 private float dirtScale = 16; 90 private float rockScale = 128; 91 92 private boolean raiseTerrain = false; 93 private boolean lowerTerrain = false; 94 95 private Geometry marker; 96 private Geometry markerNormal; 97 98 public static void main(String[] args) { 99 TerrainTestModifyHeight app = new TerrainTestModifyHeight(); 100 app.start(); 101 } 102 103 @Override 104 public void simpleUpdate(float tpf){ 105 Vector3f intersection = getWorldIntersection(); 106 updateHintText(intersection); 107 108 if (raiseTerrain){ 109 110 if (intersection != null) { 111 adjustHeight(intersection, 64, tpf * 60); 112 } 113 }else if (lowerTerrain){ 114 if (intersection != null) { 115 adjustHeight(intersection, 64, -tpf * 60); 116 } 117 } 118 119 if (terrain != null && intersection != null) { 120 float h = terrain.getHeight(new Vector2f(intersection.x, intersection.z)); 121 Vector3f tl = terrain.getWorldTranslation(); 122 marker.setLocalTranslation(tl.add(new Vector3f(intersection.x, h, intersection.z)) ); 123 markerNormal.setLocalTranslation(tl.add(new Vector3f(intersection.x, h, intersection.z)) ); 124 125 Vector3f normal = terrain.getNormal(new Vector2f(intersection.x, intersection.z)); 126 ((Arrow)markerNormal.getMesh()).setArrowExtent(normal); 127 } 128 } 129 130 @Override 131 public void simpleInitApp() { 132 loadHintText(); 133 initCrossHairs(); 134 setupKeys(); 135 136 createMarker(); 137 138 // WIREFRAME material 139 matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); 140 matWire.getAdditionalRenderState().setWireframe(true); 141 matWire.setColor("Color", ColorRGBA.Green); 142 143 createTerrain(); 144 //createTerrainGrid(); 145 146 DirectionalLight light = new DirectionalLight(); 147 light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize()); 148 rootNode.addLight(light); 149 150 AmbientLight ambLight = new AmbientLight(); 151 ambLight.setColor(new ColorRGBA(1f, 1f, 0.8f, 0.2f)); 152 rootNode.addLight(ambLight); 153 154 cam.setLocation(new Vector3f(0, 256, 0)); 155 cam.lookAtDirection(new Vector3f(0, -1f, 0).normalizeLocal(), Vector3f.UNIT_X); 156 } 157 158 public void loadHintText() { 159 hintText = new BitmapText(guiFont, false); 160 hintText.setLocalTranslation(0, getCamera().getHeight(), 0); 161 hintText.setText("Hit 1 to raise terrain, hit 2 to lower terrain"); 162 guiNode.attachChild(hintText); 163 } 164 165 public void updateHintText(Vector3f target) { 166 int x = (int) getCamera().getLocation().x; 167 int y = (int) getCamera().getLocation().y; 168 int z = (int) getCamera().getLocation().z; 169 String targetText = ""; 170 if (target!= null) 171 targetText = " intersect: "+target.toString(); 172 hintText.setText("Press left mouse button to raise terrain, press right mouse button to lower terrain. " + x + "," + y + "," + z+targetText); 173 } 174 175 protected void initCrossHairs() { 176 BitmapText ch = new BitmapText(guiFont, false); 177 ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); 178 ch.setText("+"); // crosshairs 179 ch.setLocalTranslation( // center 180 settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2, 181 settings.getHeight() / 2 + ch.getLineHeight() / 2, 0); 182 guiNode.attachChild(ch); 183 } 184 185 private void setupKeys() { 186 flyCam.setMoveSpeed(100); 187 inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T)); 188 inputManager.addListener(actionListener, "wireframe"); 189 inputManager.addMapping("Raise", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); 190 inputManager.addListener(actionListener, "Raise"); 191 inputManager.addMapping("Lower", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); 192 inputManager.addListener(actionListener, "Lower"); 193 } 194 private ActionListener actionListener = new ActionListener() { 195 196 public void onAction(String name, boolean pressed, float tpf) { 197 if (name.equals("wireframe") && !pressed) { 198 wireframe = !wireframe; 199 if (!wireframe) { 200 terrain.setMaterial(matWire); 201 } else { 202 terrain.setMaterial(matTerrain); 203 } 204 } else if (name.equals("Raise")) { 205 raiseTerrain = pressed; 206 } else if (name.equals("Lower")) { 207 lowerTerrain = pressed; 208 } 209 } 210 }; 211 212 private void adjustHeight(Vector3f loc, float radius, float height) { 213 214 // offset it by radius because in the loop we iterate through 2 radii 215 int radiusStepsX = (int) (radius / terrain.getLocalScale().x); 216 int radiusStepsZ = (int) (radius / terrain.getLocalScale().z); 217 218 float xStepAmount = terrain.getLocalScale().x; 219 float zStepAmount = terrain.getLocalScale().z; 220 long start = System.currentTimeMillis(); 221 List<Vector2f> locs = new ArrayList<Vector2f>(); 222 List<Float> heights = new ArrayList<Float>(); 223 224 for (int z = -radiusStepsZ; z < radiusStepsZ; z++) { 225 for (int x = -radiusStepsX; x < radiusStepsX; x++) { 226 227 float locX = loc.x + (x * xStepAmount); 228 float locZ = loc.z + (z * zStepAmount); 229 230 if (isInRadius(locX - loc.x, locZ - loc.z, radius)) { 231 // see if it is in the radius of the tool 232 float h = calculateHeight(radius, height, locX - loc.x, locZ - loc.z); 233 locs.add(new Vector2f(locX, locZ)); 234 heights.add(h); 235 } 236 } 237 } 238 239 terrain.adjustHeight(locs, heights); 240 //System.out.println("Modified "+locs.size()+" points, took: " + (System.currentTimeMillis() - start)+" ms"); 241 terrain.updateModelBound(); 242 } 243 244 private boolean isInRadius(float x, float y, float radius) { 245 Vector2f point = new Vector2f(x, y); 246 // return true if the distance is less than equal to the radius 247 return point.length() <= radius; 248 } 249 250 private float calculateHeight(float radius, float heightFactor, float x, float z) { 251 // find percentage for each 'unit' in radius 252 Vector2f point = new Vector2f(x, z); 253 float val = point.length() / radius; 254 val = 1 - val; 255 if (val <= 0) { 256 val = 0; 257 } 258 return heightFactor * val; 259 } 260 261 private Vector3f getWorldIntersection() { 262 Vector3f origin = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.0f); 263 Vector3f direction = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.3f); 264 direction.subtractLocal(origin).normalizeLocal(); 265 266 Ray ray = new Ray(origin, direction); 267 CollisionResults results = new CollisionResults(); 268 int numCollisions = terrain.collideWith(ray, results); 269 if (numCollisions > 0) { 270 CollisionResult hit = results.getClosestCollision(); 271 return hit.getContactPoint(); 272 } 273 return null; 274 } 275 276 private void createTerrain() { 277 // First, we load up our textures and the heightmap texture for the terrain 278 279 // TERRAIN TEXTURE material 280 matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); 281 matTerrain.setBoolean("useTriPlanarMapping", false); 282 matTerrain.setBoolean("WardIso", true); 283 matTerrain.setFloat("Shininess", 0); 284 285 // ALPHA map (for splat textures) 286 matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); 287 288 // GRASS texture 289 Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); 290 grass.setWrap(WrapMode.Repeat); 291 matTerrain.setTexture("DiffuseMap", grass); 292 matTerrain.setFloat("DiffuseMap_0_scale", grassScale); 293 294 // DIRT texture 295 Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); 296 dirt.setWrap(WrapMode.Repeat); 297 matTerrain.setTexture("DiffuseMap_1", dirt); 298 matTerrain.setFloat("DiffuseMap_1_scale", dirtScale); 299 300 // ROCK texture 301 Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); 302 rock.setWrap(WrapMode.Repeat); 303 matTerrain.setTexture("DiffuseMap_2", rock); 304 matTerrain.setFloat("DiffuseMap_2_scale", rockScale); 305 306 // HEIGHTMAP image (for the terrain heightmap) 307 Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); 308 AbstractHeightMap heightmap = null; 309 try { 310 heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.5f); 311 heightmap.load(); 312 heightmap.smooth(0.9f, 1); 313 314 } catch (Exception e) { 315 e.printStackTrace(); 316 } 317 318 // CREATE THE TERRAIN 319 terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); 320 TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); 321 control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier 322 terrain.addControl(control); 323 terrain.setMaterial(matTerrain); 324 terrain.setLocalTranslation(0, -100, 0); 325 terrain.setLocalScale(2.5f, 0.5f, 2.5f); 326 rootNode.attachChild(terrain); 327 } 328 329 private void createTerrainGrid() { 330 331 // TERRAIN TEXTURE material 332 matTerrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md"); 333 334 // Parameters to material: 335 // regionXColorMap: X = 1..4 the texture that should be appliad to state X 336 // regionX: a Vector3f containing the following information: 337 // regionX.x: the start height of the region 338 // regionX.y: the end height of the region 339 // regionX.z: the texture scale for the region 340 // it might not be the most elegant way for storing these 3 values, but it packs the data nicely :) 341 // slopeColorMap: the texture to be used for cliffs, and steep mountain sites 342 // slopeTileFactor: the texture scale for slopes 343 // terrainSize: the total size of the terrain (used for scaling the texture) 344 // GRASS texture 345 Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); 346 grass.setWrap(WrapMode.Repeat); 347 matTerrain.setTexture("region1ColorMap", grass); 348 matTerrain.setVector3("region1", new Vector3f(88, 200, this.grassScale)); 349 350 // DIRT texture 351 Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); 352 dirt.setWrap(WrapMode.Repeat); 353 matTerrain.setTexture("region2ColorMap", dirt); 354 matTerrain.setVector3("region2", new Vector3f(0, 90, this.dirtScale)); 355 356 // ROCK texture 357 Texture rock = assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg"); 358 rock.setWrap(WrapMode.Repeat); 359 matTerrain.setTexture("region3ColorMap", rock); 360 matTerrain.setVector3("region3", new Vector3f(198, 260, this.rockScale)); 361 362 matTerrain.setTexture("region4ColorMap", rock); 363 matTerrain.setVector3("region4", new Vector3f(198, 260, this.rockScale)); 364 365 matTerrain.setTexture("slopeColorMap", rock); 366 matTerrain.setFloat("slopeTileFactor", 32); 367 368 matTerrain.setFloat("terrainSize", 513); 369 370 FractalSum base = new FractalSum(); 371 base.setRoughness(0.7f); 372 base.setFrequency(1.0f); 373 base.setAmplitude(1.0f); 374 base.setLacunarity(2.12f); 375 base.setOctaves(8); 376 base.setScale(0.02125f); 377 base.addModulator(new NoiseModulator() { 378 @Override 379 public float value(float... in) { 380 return ShaderUtils.clamp(in[0] * 0.5f + 0.5f, 0, 1); 381 } 382 }); 383 384 FilteredBasis ground = new FilteredBasis(base); 385 386 PerturbFilter perturb = new PerturbFilter(); 387 perturb.setMagnitude(0.119f); 388 389 OptimizedErode therm = new OptimizedErode(); 390 therm.setRadius(5); 391 therm.setTalus(0.011f); 392 393 SmoothFilter smooth = new SmoothFilter(); 394 smooth.setRadius(1); 395 smooth.setEffect(0.7f); 396 397 IterativeFilter iterate = new IterativeFilter(); 398 iterate.addPreFilter(perturb); 399 iterate.addPostFilter(smooth); 400 iterate.setFilter(therm); 401 iterate.setIterations(1); 402 403 ground.addPreFilter(iterate); 404 405 this.terrain = new TerrainGrid("terrain", 65, 257, new FractalTileLoader(ground, 256f)); 406 407 408 terrain.setMaterial(matTerrain); 409 terrain.setLocalTranslation(0, 0, 0); 410 terrain.setLocalScale(2f, 1f, 2f); 411 412 rootNode.attachChild(this.terrain); 413 414 TerrainLodControl control = new TerrainLodControl(this.terrain, getCamera()); 415 this.terrain.addControl(control); 416 } 417 418 private void createMarker() { 419 // collision marker 420 Sphere sphere = new Sphere(8, 8, 0.5f); 421 marker = new Geometry("Marker"); 422 marker.setMesh(sphere); 423 424 Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); 425 mat.setColor("Color", new ColorRGBA(251f/255f, 130f/255f, 0f, 0.6f)); 426 mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); 427 428 marker.setMaterial(mat); 429 rootNode.attachChild(marker); 430 431 432 // surface normal marker 433 Arrow arrow = new Arrow(new Vector3f(0,1,0)); 434 markerNormal = new Geometry("MarkerNormal"); 435 markerNormal.setMesh(arrow); 436 markerNormal.setMaterial(mat); 437 rootNode.attachChild(markerNormal); 438 } 439} 440