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.export.Savable;
36 import com.jme3.export.binary.BinaryExporter;
37 import com.jme3.export.binary.BinaryImporter;
38 import com.jme3.font.BitmapText;
39 import com.jme3.input.KeyInput;
40 import com.jme3.input.controls.ActionListener;
41 import com.jme3.input.controls.KeyTrigger;
42 import com.jme3.light.DirectionalLight;
43 import com.jme3.material.Material;
44 import com.jme3.math.ColorRGBA;
45 import com.jme3.math.Vector3f;
46 import com.jme3.scene.Node;
47 import com.jme3.terrain.Terrain;
48 import com.jme3.terrain.geomipmap.TerrainLodControl;
49 import com.jme3.terrain.geomipmap.TerrainQuad;
50 import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
51 import com.jme3.terrain.heightmap.AbstractHeightMap;
52 import com.jme3.terrain.heightmap.ImageBasedHeightMap;
53 import com.jme3.texture.Texture;
54 import com.jme3.texture.Texture.WrapMode;
55 import java.io.*;
56 import java.util.logging.Level;
57 import java.util.logging.Logger;
58 
59 /**
60  * Saves and loads terrain.
61  *
62  * @author Brent Owens
63  */
64 public class TerrainTestReadWrite extends SimpleApplication {
65 
66     private Terrain terrain;
67     protected BitmapText hintText;
68     private float grassScale = 64;
69     private float dirtScale = 16;
70     private float rockScale = 128;
71     private Material matTerrain;
72     private Material matWire;
73 
main(String[] args)74     public static void main(String[] args) {
75         TerrainTestReadWrite app = new TerrainTestReadWrite();
76         app.start();
77         //testHeightmapBuilding();
78     }
79 
80     @Override
initialize()81     public void initialize() {
82         super.initialize();
83 
84         loadHintText();
85     }
86 
87     @Override
simpleInitApp()88     public void simpleInitApp() {
89 
90 
91         createControls();
92         createMap();
93     }
94 
createMap()95     private void createMap() {
96         matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
97         matTerrain.setBoolean("useTriPlanarMapping", false);
98         matTerrain.setBoolean("WardIso", true);
99 
100         // ALPHA map (for splat textures)
101         matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
102 
103         // HEIGHTMAP image (for the terrain heightmap)
104         Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
105 
106         // GRASS texture
107         Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
108         grass.setWrap(WrapMode.Repeat);
109         matTerrain.setTexture("DiffuseMap", grass);
110         matTerrain.setFloat("DiffuseMap_0_scale", grassScale);
111 
112 
113         // DIRT texture
114         Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
115         dirt.setWrap(WrapMode.Repeat);
116         matTerrain.setTexture("DiffuseMap_1", dirt);
117         matTerrain.setFloat("DiffuseMap_1_scale", dirtScale);
118 
119         // ROCK texture
120         Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
121         rock.setWrap(WrapMode.Repeat);
122         matTerrain.setTexture("DiffuseMap_2", rock);
123         matTerrain.setFloat("DiffuseMap_2_scale", rockScale);
124 
125 
126         Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
127         normalMap0.setWrap(WrapMode.Repeat);
128         Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
129         normalMap1.setWrap(WrapMode.Repeat);
130         Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
131         normalMap2.setWrap(WrapMode.Repeat);
132         matTerrain.setTexture("NormalMap", normalMap0);
133         matTerrain.setTexture("NormalMap_1", normalMap2);
134         matTerrain.setTexture("NormalMap_2", normalMap2);
135 
136         matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
137         matWire.getAdditionalRenderState().setWireframe(true);
138         matWire.setColor("Color", ColorRGBA.Green);
139 
140 
141         // CREATE HEIGHTMAP
142         AbstractHeightMap heightmap = null;
143         try {
144             heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f);
145             heightmap.load();
146 
147         } catch (Exception e) {
148             e.printStackTrace();
149         }
150 
151         if (new File("terrainsave.jme").exists()) {
152             loadTerrain();
153         } else {
154             // create the terrain as normal, and give it a control for LOD management
155             TerrainQuad terrainQuad = new TerrainQuad("terrain", 65, 129, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations
156             TerrainLodControl control = new TerrainLodControl(terrainQuad, getCamera());
157             control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
158             terrainQuad.addControl(control);
159             terrainQuad.setMaterial(matTerrain);
160             terrainQuad.setLocalTranslation(0, -100, 0);
161             terrainQuad.setLocalScale(4f, 0.25f, 4f);
162             rootNode.attachChild(terrainQuad);
163 
164             this.terrain = terrainQuad;
165         }
166 
167         DirectionalLight light = new DirectionalLight();
168         light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize());
169         rootNode.addLight(light);
170     }
171 
172     /**
173      * Create the save and load actions and add them to the input listener
174      */
createControls()175     private void createControls() {
176         flyCam.setMoveSpeed(50);
177         cam.setLocation(new Vector3f(0, 100, 0));
178 
179         inputManager.addMapping("save", new KeyTrigger(KeyInput.KEY_T));
180         inputManager.addListener(saveActionListener, "save");
181 
182         inputManager.addMapping("load", new KeyTrigger(KeyInput.KEY_Y));
183         inputManager.addListener(loadActionListener, "load");
184 
185         inputManager.addMapping("clone", new KeyTrigger(KeyInput.KEY_C));
186         inputManager.addListener(cloneActionListener, "clone");
187     }
188 
loadHintText()189     public void loadHintText() {
190         hintText = new BitmapText(guiFont, false);
191         hintText.setSize(guiFont.getCharSet().getRenderedSize());
192         hintText.setLocalTranslation(0, getCamera().getHeight(), 0);
193         hintText.setText("Hit T to save, and Y to load");
194         guiNode.attachChild(hintText);
195     }
196     private ActionListener saveActionListener = new ActionListener() {
197 
198         public void onAction(String name, boolean pressed, float tpf) {
199             if (name.equals("save") && !pressed) {
200 
201                 FileOutputStream fos = null;
202                 try {
203                     long start = System.currentTimeMillis();
204                     fos = new FileOutputStream(new File("terrainsave.jme"));
205 
206                     // we just use the exporter and pass in the terrain
207                     BinaryExporter.getInstance().save((Savable)terrain, new BufferedOutputStream(fos));
208 
209                     fos.flush();
210                     float duration = (System.currentTimeMillis() - start) / 1000.0f;
211                     System.out.println("Save took " + duration + " seconds");
212                 } catch (IOException ex) {
213                     Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, ex);
214                 } finally {
215                     try {
216                         if (fos != null) {
217                             fos.close();
218                         }
219                     } catch (IOException e) {
220                         Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, e);
221                     }
222                 }
223             }
224         }
225     };
226 
loadTerrain()227     private void loadTerrain() {
228         FileInputStream fis = null;
229         try {
230             long start = System.currentTimeMillis();
231             // remove the existing terrain and detach it from the root node.
232             if (terrain != null) {
233                 Node existingTerrain = (Node)terrain;
234                 existingTerrain.removeFromParent();
235                 existingTerrain.removeControl(TerrainLodControl.class);
236                 existingTerrain.detachAllChildren();
237                 terrain = null;
238             }
239 
240             // import the saved terrain, and attach it back to the root node
241             File f = new File("terrainsave.jme");
242             fis = new FileInputStream(f);
243             BinaryImporter imp = BinaryImporter.getInstance();
244             imp.setAssetManager(assetManager);
245             terrain = (TerrainQuad) imp.load(new BufferedInputStream(fis));
246             rootNode.attachChild((Node)terrain);
247 
248             float duration = (System.currentTimeMillis() - start) / 1000.0f;
249             System.out.println("Load took " + duration + " seconds");
250 
251             // now we have to add back the camera to the LOD control
252             TerrainLodControl lodControl = ((Node)terrain).getControl(TerrainLodControl.class);
253             if (lodControl != null)
254                 lodControl.setCamera(getCamera());
255 
256         } catch (IOException ex) {
257             Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, ex);
258         } finally {
259             try {
260                 if (fis != null) {
261                     fis.close();
262                 }
263             } catch (IOException ex) {
264                 Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, ex);
265             }
266         }
267     }
268     private ActionListener loadActionListener = new ActionListener() {
269 
270         public void onAction(String name, boolean pressed, float tpf) {
271             if (name.equals("load") && !pressed) {
272                 loadTerrain();
273             }
274         }
275     };
276     private ActionListener cloneActionListener = new ActionListener() {
277 
278         public void onAction(String name, boolean pressed, float tpf) {
279             if (name.equals("clone") && !pressed) {
280 
281                 Terrain clone = (Terrain) ((Node)terrain).clone();
282                 ((Node)terrain).removeFromParent();
283                 terrain = clone;
284                 getRootNode().attachChild((Node)terrain);
285             }
286         }
287     };
288 
289     // no junit tests, so this has to be hand-tested:
testHeightmapBuilding()290     private static void testHeightmapBuilding() {
291         int s = 9;
292         int b = 3;
293         float[] hm = new float[s * s];
294         for (int i = 0; i < s; i++) {
295             for (int j = 0; j < s; j++) {
296                 hm[(i * s) + j] = i * j;
297             }
298         }
299 
300         for (int i = 0; i < s; i++) {
301             for (int j = 0; j < s; j++) {
302                 System.out.print(hm[i * s + j] + " ");
303             }
304             System.out.println("");
305         }
306 
307         TerrainQuad terrain = new TerrainQuad("terrain", b, s, hm);
308         float[] hm2 = terrain.getHeightMap();
309         boolean failed = false;
310         for (int i = 0; i < s * s; i++) {
311             if (hm[i] != hm2[i]) {
312                 failed = true;
313             }
314         }
315 
316         System.out.println("");
317         if (failed) {
318             System.out.println("Terrain heightmap building FAILED!!!");
319             for (int i = 0; i < s; i++) {
320                 for (int j = 0; j < s; j++) {
321                     System.out.print(hm2[i * s + j] + " ");
322                 }
323                 System.out.println("");
324             }
325         } else {
326             System.out.println("Terrain heightmap building PASSED");
327         }
328     }
329 }
330