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.export.Savable;
36import com.jme3.export.binary.BinaryExporter;
37import com.jme3.export.binary.BinaryImporter;
38import com.jme3.font.BitmapText;
39import com.jme3.input.KeyInput;
40import com.jme3.input.controls.ActionListener;
41import com.jme3.input.controls.KeyTrigger;
42import com.jme3.light.DirectionalLight;
43import com.jme3.material.Material;
44import com.jme3.math.ColorRGBA;
45import com.jme3.math.Vector3f;
46import com.jme3.scene.Node;
47import com.jme3.terrain.Terrain;
48import com.jme3.terrain.geomipmap.TerrainLodControl;
49import com.jme3.terrain.geomipmap.TerrainQuad;
50import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
51import com.jme3.terrain.heightmap.AbstractHeightMap;
52import com.jme3.terrain.heightmap.ImageBasedHeightMap;
53import com.jme3.texture.Texture;
54import com.jme3.texture.Texture.WrapMode;
55import java.io.*;
56import java.util.logging.Level;
57import java.util.logging.Logger;
58
59/**
60 * Saves and loads terrain.
61 *
62 * @author Brent Owens
63 */
64public 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
74    public static void main(String[] args) {
75        TerrainTestReadWrite app = new TerrainTestReadWrite();
76        app.start();
77        //testHeightmapBuilding();
78    }
79
80    @Override
81    public void initialize() {
82        super.initialize();
83
84        loadHintText();
85    }
86
87    @Override
88    public void simpleInitApp() {
89
90
91        createControls();
92        createMap();
93    }
94
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     */
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
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
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:
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