Home | History | Annotate | Download | only in terrain
      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.collision.CollisionResult;
     36 import com.jme3.collision.CollisionResults;
     37 import com.jme3.font.BitmapText;
     38 import com.jme3.input.KeyInput;
     39 import com.jme3.input.MouseInput;
     40 import com.jme3.input.controls.ActionListener;
     41 import com.jme3.input.controls.KeyTrigger;
     42 import com.jme3.input.controls.MouseButtonTrigger;
     43 import com.jme3.light.AmbientLight;
     44 import com.jme3.light.DirectionalLight;
     45 import com.jme3.material.Material;
     46 import com.jme3.material.RenderState.BlendMode;
     47 import com.jme3.math.ColorRGBA;
     48 import com.jme3.math.Ray;
     49 import com.jme3.math.Vector2f;
     50 import com.jme3.math.Vector3f;
     51 import com.jme3.scene.Geometry;
     52 import com.jme3.scene.debug.Arrow;
     53 import com.jme3.scene.shape.Sphere;
     54 import com.jme3.terrain.geomipmap.TerrainGrid;
     55 import com.jme3.terrain.geomipmap.TerrainLodControl;
     56 import com.jme3.terrain.geomipmap.TerrainQuad;
     57 import com.jme3.terrain.geomipmap.grid.FractalTileLoader;
     58 import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
     59 import com.jme3.terrain.heightmap.AbstractHeightMap;
     60 import com.jme3.terrain.heightmap.ImageBasedHeightMap;
     61 import com.jme3.terrain.noise.ShaderUtils;
     62 import com.jme3.terrain.noise.basis.FilteredBasis;
     63 import com.jme3.terrain.noise.filter.IterativeFilter;
     64 import com.jme3.terrain.noise.filter.OptimizedErode;
     65 import com.jme3.terrain.noise.filter.PerturbFilter;
     66 import com.jme3.terrain.noise.filter.SmoothFilter;
     67 import com.jme3.terrain.noise.fractal.FractalSum;
     68 import com.jme3.terrain.noise.modulator.NoiseModulator;
     69 import com.jme3.texture.Texture;
     70 import com.jme3.texture.Texture.WrapMode;
     71 import java.util.ArrayList;
     72 import java.util.List;
     73 
     74 /**
     75  *
     76  * @author Brent Owens
     77  */
     78 public 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