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.font.BitmapText;
36import com.jme3.input.KeyInput;
37import com.jme3.input.controls.ActionListener;
38import com.jme3.input.controls.KeyTrigger;
39import com.jme3.light.DirectionalLight;
40import com.jme3.light.PointLight;
41import com.jme3.material.Material;
42import com.jme3.math.ColorRGBA;
43import com.jme3.math.Vector3f;
44import com.jme3.scene.Geometry;
45import com.jme3.terrain.geomipmap.TerrainLodControl;
46import com.jme3.terrain.geomipmap.TerrainQuad;
47import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
48import com.jme3.terrain.heightmap.AbstractHeightMap;
49import com.jme3.terrain.heightmap.ImageBasedHeightMap;
50import com.jme3.texture.Texture;
51import com.jme3.texture.Texture.WrapMode;
52import com.jme3.asset.TextureKey;
53
54/**
55 * Demonstrates how to use terrain.
56 * The base terrain class it uses is TerrainQuad, which is a quad tree of actual
57 * meshes called TerainPatches.
58 * There are a couple options for the terrain in this test:
59 * The first is wireframe mode. Here you can see the underlying trianglestrip structure.
60 * You will notice some off lines; these are degenerate triangles and are part of the
61 * trianglestrip. They are only noticeable in wireframe mode.
62 * Second is Tri-Planar texture mode. Here the textures are rendered on all 3 axes and
63 * then blended together to reduce distortion and stretching.
64 * Third, which you have to modify the code to see, is Entropy LOD calculations.
65 * In the constructor for the TerrainQuad, un-comment the final parameter that is
66 * the LodPerspectiveCalculatorFactory. Then you will see the terrain flicker to start
67 * while it calculates the entropies. Once it is done, it will pick the best LOD value
68 * based on entropy. This method reduces "popping" of terrain greatly when LOD levels
69 * change. It is highly suggested you use it in your app.
70 *
71 * @author bowens
72 */
73public class TerrainTest extends SimpleApplication {
74
75    private TerrainQuad terrain;
76    Material matRock;
77    Material matWire;
78    boolean wireframe = false;
79    boolean triPlanar = false;
80    protected BitmapText hintText;
81    PointLight pl;
82    Geometry lightMdl;
83    private float grassScale = 64;
84    private float dirtScale = 16;
85    private float rockScale = 128;
86
87    public static void main(String[] args) {
88        TerrainTest app = new TerrainTest();
89        app.start();
90    }
91
92    @Override
93    public void initialize() {
94        super.initialize();
95
96        loadHintText();
97    }
98
99    @Override
100    public void simpleInitApp() {
101        setupKeys();
102
103        // First, we load up our textures and the heightmap texture for the terrain
104
105        // TERRAIN TEXTURE material
106        matRock = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
107        matRock.setBoolean("useTriPlanarMapping", false);
108
109        // ALPHA map (for splat textures)
110        matRock.setTexture("Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
111
112        // HEIGHTMAP image (for the terrain heightmap)
113        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
114
115        // GRASS texture
116        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
117        grass.setWrap(WrapMode.Repeat);
118        matRock.setTexture("Tex1", grass);
119        matRock.setFloat("Tex1Scale", grassScale);
120
121        // DIRT texture
122        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
123        dirt.setWrap(WrapMode.Repeat);
124        matRock.setTexture("Tex2", dirt);
125        matRock.setFloat("Tex2Scale", dirtScale);
126
127        // ROCK texture
128        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
129        rock.setWrap(WrapMode.Repeat);
130        matRock.setTexture("Tex3", rock);
131        matRock.setFloat("Tex3Scale", rockScale);
132
133        // WIREFRAME material
134        matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
135        matWire.getAdditionalRenderState().setWireframe(true);
136        matWire.setColor("Color", ColorRGBA.Green);
137
138        // CREATE HEIGHTMAP
139        AbstractHeightMap heightmap = null;
140        try {
141            //heightmap = new HillHeightMap(1025, 1000, 50, 100, (byte) 3);
142
143            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f);
144            heightmap.load();
145
146        } catch (Exception e) {
147            e.printStackTrace();
148        }
149
150        /*
151         * Here we create the actual terrain. The tiles will be 65x65, and the total size of the
152         * terrain will be 513x513. It uses the heightmap we created to generate the height values.
153         */
154        /**
155         * Optimal terrain patch size is 65 (64x64).
156         * The total size is up to you. At 1025 it ran fine for me (200+FPS), however at
157         * size=2049, it got really slow. But that is a jump from 2 million to 8 million triangles...
158         */
159        terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());
160        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
161        control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
162        terrain.addControl(control);
163        terrain.setMaterial(matRock);
164        terrain.setLocalTranslation(0, -100, 0);
165        terrain.setLocalScale(2f, 1f, 2f);
166        rootNode.attachChild(terrain);
167
168        DirectionalLight light = new DirectionalLight();
169        light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize());
170        rootNode.addLight(light);
171
172        cam.setLocation(new Vector3f(0, 10, -10));
173        cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y);
174    }
175
176    public void loadHintText() {
177        hintText = new BitmapText(guiFont, false);
178        hintText.setSize(guiFont.getCharSet().getRenderedSize());
179        hintText.setLocalTranslation(0, getCamera().getHeight(), 0);
180        hintText.setText("Hit T to switch to wireframe,  P to switch to tri-planar texturing");
181        guiNode.attachChild(hintText);
182    }
183
184    private void setupKeys() {
185        flyCam.setMoveSpeed(50);
186        inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));
187        inputManager.addListener(actionListener, "wireframe");
188        inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P));
189        inputManager.addListener(actionListener, "triPlanar");
190    }
191    private ActionListener actionListener = new ActionListener() {
192
193        public void onAction(String name, boolean pressed, float tpf) {
194            if (name.equals("wireframe") && !pressed) {
195                wireframe = !wireframe;
196                if (!wireframe) {
197                    terrain.setMaterial(matWire);
198                } else {
199                    terrain.setMaterial(matRock);
200                }
201            } else if (name.equals("triPlanar") && !pressed) {
202                triPlanar = !triPlanar;
203                if (triPlanar) {
204                    matRock.setBoolean("useTriPlanarMapping", true);
205                    // planar textures don't use the mesh's texture coordinates but real world coordinates,
206                    // so we need to convert these texture coordinate scales into real world scales so it looks
207                    // the same when we switch to/from tr-planar mode
208                    matRock.setFloat("Tex1Scale", 1f / (float) (512f / grassScale));
209                    matRock.setFloat("Tex2Scale", 1f / (float) (512f / dirtScale));
210                    matRock.setFloat("Tex3Scale", 1f / (float) (512f / rockScale));
211                } else {
212                    matRock.setBoolean("useTriPlanarMapping", false);
213                    matRock.setFloat("Tex1Scale", grassScale);
214                    matRock.setFloat("Tex2Scale", dirtScale);
215                    matRock.setFloat("Tex3Scale", rockScale);
216                }
217            }
218        }
219    };
220}
221