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.bounding.BoundingBox;
36import com.jme3.font.BitmapText;
37import com.jme3.input.KeyInput;
38import com.jme3.input.controls.ActionListener;
39import com.jme3.input.controls.KeyTrigger;
40import com.jme3.light.DirectionalLight;
41import com.jme3.light.PointLight;
42import com.jme3.material.Material;
43import com.jme3.math.ColorRGBA;
44import com.jme3.math.Vector3f;
45import com.jme3.scene.Geometry;
46import com.jme3.scene.Spatial;
47import com.jme3.terrain.geomipmap.TerrainLodControl;
48import com.jme3.terrain.geomipmap.TerrainQuad;
49import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
50import com.jme3.terrain.heightmap.AbstractHeightMap;
51import com.jme3.terrain.heightmap.ImageBasedHeightMap;
52import com.jme3.texture.Texture;
53import com.jme3.texture.Texture.WrapMode;
54import com.jme3.util.SkyFactory;
55import com.jme3.scene.Node;
56import com.jme3.scene.debug.Arrow;
57
58/**
59 * Uses the terrain's lighting texture with normal maps and lights.
60 *
61 * @author bowens
62 */
63public class TerrainTestAdvanced extends SimpleApplication {
64
65    private TerrainQuad terrain;
66    Material matTerrain;
67    Material matWire;
68    boolean wireframe = false;
69    boolean triPlanar = false;
70    boolean wardiso = false;
71    boolean minnaert = false;
72    protected BitmapText hintText;
73    PointLight pl;
74    Geometry lightMdl;
75    private float grassScale = 64;
76    private float dirtScale = 16;
77    private float rockScale = 128;
78
79    public static void main(String[] args) {
80        TerrainTestAdvanced app = new TerrainTestAdvanced();
81        app.start();
82    }
83
84    @Override
85    public void initialize() {
86        super.initialize();
87
88        loadHintText();
89    }
90
91    @Override
92    public void simpleInitApp() {
93        setupKeys();
94
95        // First, we load up our textures and the heightmap texture for the terrain
96
97        // TERRAIN TEXTURE material
98        matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
99        matTerrain.setBoolean("useTriPlanarMapping", false);
100        matTerrain.setFloat("Shininess", 0.0f);
101
102        // ALPHA map (for splat textures)
103        matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png"));
104        matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png"));
105
106        // HEIGHTMAP image (for the terrain heightmap)
107        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
108
109        // GRASS texture
110        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
111        grass.setWrap(WrapMode.Repeat);
112        //matTerrain.setTexture("DiffuseMap_1", grass);
113        //matTerrain.setFloat("DiffuseMap_1_scale", grassScale);
114
115        // DIRT texture
116        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
117        dirt.setWrap(WrapMode.Repeat);
118        matTerrain.setTexture("DiffuseMap", dirt);
119        matTerrain.setFloat("DiffuseMap_0_scale", dirtScale);
120
121        // ROCK texture
122        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
123        rock.setWrap(WrapMode.Repeat);
124        //matTerrain.setTexture("DiffuseMap_2", rock);
125        //matTerrain.setFloat("DiffuseMap_2_scale", rockScale);
126
127        // BRICK texture
128        Texture brick = assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg");
129        brick.setWrap(WrapMode.Repeat);
130        //matTerrain.setTexture("DiffuseMap_3", brick);
131        //matTerrain.setFloat("DiffuseMap_3_scale", rockScale);
132
133        // RIVER ROCK texture
134        Texture riverRock = assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg");
135        riverRock.setWrap(WrapMode.Repeat);
136        //matTerrain.setTexture("DiffuseMap_4", riverRock);
137        //matTerrain.setFloat("DiffuseMap_4_scale", rockScale);
138
139
140        Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
141        normalMap0.setWrap(WrapMode.Repeat);
142        Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
143        normalMap1.setWrap(WrapMode.Repeat);
144        Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
145        normalMap2.setWrap(WrapMode.Repeat);
146        matTerrain.setTexture("NormalMap", normalMap0);
147        //matTerrain.setTexture("NormalMap_1", normalMap2);
148        //matTerrain.setTexture("NormalMap_2", normalMap2);
149        //matTerrain.setTexture("NormalMap_4", normalMap2);
150
151        // WIREFRAME material
152        matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
153        matWire.getAdditionalRenderState().setWireframe(true);
154        matWire.setColor("Color", ColorRGBA.Green);
155
156        //createSky();
157
158        // CREATE HEIGHTMAP
159        AbstractHeightMap heightmap = null;
160        try {
161            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.5f);
162            heightmap.load();
163            heightmap.smooth(0.9f, 1);
164
165        } catch (Exception e) {
166            e.printStackTrace();
167        }
168
169        /*
170         * Here we create the actual terrain. The tiles will be 65x65, and the total size of the
171         * terrain will be 513x513. It uses the heightmap we created to generate the height values.
172         */
173        /**
174         * Optimal terrain patch size is 65 (64x64).
175         * The total size is up to you. At 1025 it ran fine for me (200+FPS), however at
176         * size=2049, it got really slow. But that is a jump from 2 million to 8 million triangles...
177         */
178        terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations
179        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
180        control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
181        terrain.addControl(control);
182        terrain.setMaterial(matTerrain);
183        terrain.setModelBound(new BoundingBox());
184        terrain.updateModelBound();
185        terrain.setLocalTranslation(0, -100, 0);
186        terrain.setLocalScale(1f, 1f, 1f);
187        rootNode.attachChild(terrain);
188
189        Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m");
190        //terrain.generateDebugTangents(debugMat);
191
192        DirectionalLight light = new DirectionalLight();
193        light.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalize());
194        rootNode.addLight(light);
195
196        cam.setLocation(new Vector3f(0, 10, -10));
197        cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y);
198        flyCam.setMoveSpeed(400);
199
200        rootNode.attachChild(createAxisMarker(20));
201    }
202
203    public void loadHintText() {
204        hintText = new BitmapText(guiFont, false);
205        hintText.setSize(guiFont.getCharSet().getRenderedSize());
206        hintText.setLocalTranslation(0, getCamera().getHeight(), 0);
207        hintText.setText("Hit T to switch to wireframe,  P to switch to tri-planar texturing");
208        guiNode.attachChild(hintText);
209    }
210
211    private void setupKeys() {
212        flyCam.setMoveSpeed(50);
213        inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));
214        inputManager.addListener(actionListener, "wireframe");
215        inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P));
216        inputManager.addListener(actionListener, "triPlanar");
217        inputManager.addMapping("WardIso", new KeyTrigger(KeyInput.KEY_9));
218        inputManager.addListener(actionListener, "WardIso");
219        inputManager.addMapping("Minnaert", new KeyTrigger(KeyInput.KEY_0));
220        inputManager.addListener(actionListener, "Minnaert");
221    }
222    private ActionListener actionListener = new ActionListener() {
223
224        public void onAction(String name, boolean pressed, float tpf) {
225            if (name.equals("wireframe") && !pressed) {
226                wireframe = !wireframe;
227                if (!wireframe) {
228                    terrain.setMaterial(matWire);
229                } else {
230                    terrain.setMaterial(matTerrain);
231                }
232            } else if (name.equals("triPlanar") && !pressed) {
233                triPlanar = !triPlanar;
234                if (triPlanar) {
235                    matTerrain.setBoolean("useTriPlanarMapping", true);
236                    // planar textures don't use the mesh's texture coordinates but real world coordinates,
237                    // so we need to convert these texture coordinate scales into real world scales so it looks
238                    // the same when we switch to/from tr-planar mode
239                    matTerrain.setFloat("DiffuseMap_0_scale", 1f / (float) (512f / grassScale));
240                    matTerrain.setFloat("DiffuseMap_1_scale", 1f / (float) (512f / dirtScale));
241                    matTerrain.setFloat("DiffuseMap_2_scale", 1f / (float) (512f / rockScale));
242                    matTerrain.setFloat("DiffuseMap_3_scale", 1f / (float) (512f / rockScale));
243                    matTerrain.setFloat("DiffuseMap_4_scale", 1f / (float) (512f / rockScale));
244                } else {
245                    matTerrain.setBoolean("useTriPlanarMapping", false);
246                    matTerrain.setFloat("DiffuseMap_0_scale", grassScale);
247                    matTerrain.setFloat("DiffuseMap_1_scale", dirtScale);
248                    matTerrain.setFloat("DiffuseMap_2_scale", rockScale);
249                    matTerrain.setFloat("DiffuseMap_3_scale", rockScale);
250                    matTerrain.setFloat("DiffuseMap_4_scale", rockScale);
251                }
252            }
253        }
254    };
255
256    private void createSky() {
257        Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg");
258        Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg");
259        Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg");
260        Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg");
261        Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg");
262        Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg");
263
264        Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down);
265        rootNode.attachChild(sky);
266    }
267
268    protected Node createAxisMarker(float arrowSize) {
269
270        Material redMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
271        redMat.getAdditionalRenderState().setWireframe(true);
272        redMat.setColor("Color", ColorRGBA.Red);
273
274        Material greenMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
275        greenMat.getAdditionalRenderState().setWireframe(true);
276        greenMat.setColor("Color", ColorRGBA.Green);
277
278        Material blueMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
279        blueMat.getAdditionalRenderState().setWireframe(true);
280        blueMat.setColor("Color", ColorRGBA.Blue);
281
282        Node axis = new Node();
283
284        // create arrows
285        Geometry arrowX = new Geometry("arrowX", new Arrow(new Vector3f(arrowSize, 0, 0)));
286        arrowX.setMaterial(redMat);
287        Geometry arrowY = new Geometry("arrowY", new Arrow(new Vector3f(0, arrowSize, 0)));
288        arrowY.setMaterial(greenMat);
289        Geometry arrowZ = new Geometry("arrowZ", new Arrow(new Vector3f(0, 0, arrowSize)));
290        arrowZ.setMaterial(blueMat);
291        axis.attachChild(arrowX);
292        axis.attachChild(arrowY);
293        axis.attachChild(arrowZ);
294
295        //axis.setModelBound(new BoundingBox());
296        return axis;
297    }
298}
299