From c909219a4e45755b75949e28e8e2ff92b1bb73e4 Mon Sep 17 00:00:00 2001 From: "bre..om" Date: Wed, 12 Dec 2012 18:24:43 +0000 Subject: [PATCH] Added TerrainTestAndroid and a smaller heightmap image for it git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10025 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../jme3test/terrain/TerrainTestAndroid.java | 200 ++++++++++++++++++ .../Textures/Terrain/splat/mountains128.png | Bin 0 -> 9007 bytes 2 files changed, 200 insertions(+) create mode 100644 engine/src/test/jme3test/terrain/TerrainTestAndroid.java create mode 100644 engine/test-data/Textures/Terrain/splat/mountains128.png diff --git a/engine/src/test/jme3test/terrain/TerrainTestAndroid.java b/engine/src/test/jme3test/terrain/TerrainTestAndroid.java new file mode 100644 index 000000000..b5c225e30 --- /dev/null +++ b/engine/src/test/jme3test/terrain/TerrainTestAndroid.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.terrain; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; + +/** + * Demonstrates how to use terrain on Android. + * The only difference is it uses a much smaller heightmap so it won't use up + * all of the android device's memory. + * + * @author bowens + */ +public class TerrainTestAndroid extends SimpleApplication { + + private TerrainQuad terrain; + Material matRock; + Material matWire; + boolean wireframe = false; + boolean triPlanar = false; + protected BitmapText hintText; + PointLight pl; + Geometry lightMdl; + private float grassScale = 64; + private float dirtScale = 16; + private float rockScale = 128; + + public static void main(String[] args) { + TerrainTestAndroid app = new TerrainTestAndroid(); + app.start(); + } + + @Override + public void initialize() { + super.initialize(); + + loadHintText(); + } + + @Override + public void simpleInitApp() { + setupKeys(); + + // First, we load up our textures and the heightmap texture for the terrain + + // TERRAIN TEXTURE material + matRock = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md"); + matRock.setBoolean("useTriPlanarMapping", false); + + // ALPHA map (for splat textures) + matRock.setTexture("Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); + + // HEIGHTMAP image (for the terrain heightmap) + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains128.png"); + + // GRASS texture + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matRock.setTexture("Tex1", grass); + matRock.setFloat("Tex1Scale", grassScale); + + // DIRT texture + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matRock.setTexture("Tex2", dirt); + matRock.setFloat("Tex2Scale", dirtScale); + + // ROCK texture + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + matRock.setTexture("Tex3", rock); + matRock.setFloat("Tex3Scale", rockScale); + + // WIREFRAME material + matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matWire.getAdditionalRenderState().setWireframe(true); + matWire.setColor("Color", ColorRGBA.Green); + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f); + heightmap.load(); + + } catch (Exception e) { + e.printStackTrace(); + } + + /* + * Here we create the actual terrain. The tiles will be 33x33, and the total size of the + * terrain will be 129x129. It uses the heightmap we created to generate the height values. + */ + terrain = new TerrainQuad("terrain", 33, 129, heightmap.getHeightMap()); + TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); + control.setLodCalculator( new DistanceLodCalculator(33, 2.7f) ); // patch size, and a multiplier + terrain.addControl(control); + terrain.setMaterial(matRock); + terrain.setLocalTranslation(0, -100, 0); + terrain.setLocalScale(8f, 100f, 8f); + rootNode.attachChild(terrain); + + DirectionalLight light = new DirectionalLight(); + light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize()); + rootNode.addLight(light); + + cam.setLocation(new Vector3f(0, 10, -10)); + cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y); + } + + public void loadHintText() { + hintText = new BitmapText(guiFont, false); + hintText.setSize(guiFont.getCharSet().getRenderedSize()); + hintText.setLocalTranslation(0, getCamera().getHeight(), 0); + hintText.setText("Hit T to switch to wireframe, P to switch to tri-planar texturing"); + guiNode.attachChild(hintText); + } + + private void setupKeys() { + flyCam.setMoveSpeed(50); + inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addListener(actionListener, "wireframe"); + inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P)); + inputManager.addListener(actionListener, "triPlanar"); + } + private ActionListener actionListener = new ActionListener() { + + public void onAction(String name, boolean pressed, float tpf) { + if (name.equals("wireframe") && !pressed) { + wireframe = !wireframe; + if (!wireframe) { + terrain.setMaterial(matWire); + } else { + terrain.setMaterial(matRock); + } + } else if (name.equals("triPlanar") && !pressed) { + triPlanar = !triPlanar; + if (triPlanar) { + matRock.setBoolean("useTriPlanarMapping", true); + // planar textures don't use the mesh's texture coordinates but real world coordinates, + // so we need to convert these texture coordinate scales into real world scales so it looks + // the same when we switch to/from tr-planar mode + matRock.setFloat("Tex1Scale", 1f / (float) (512f / grassScale)); + matRock.setFloat("Tex2Scale", 1f / (float) (512f / dirtScale)); + matRock.setFloat("Tex3Scale", 1f / (float) (512f / rockScale)); + } else { + matRock.setBoolean("useTriPlanarMapping", false); + matRock.setFloat("Tex1Scale", grassScale); + matRock.setFloat("Tex2Scale", dirtScale); + matRock.setFloat("Tex3Scale", rockScale); + } + } + } + }; +} diff --git a/engine/test-data/Textures/Terrain/splat/mountains128.png b/engine/test-data/Textures/Terrain/splat/mountains128.png new file mode 100644 index 0000000000000000000000000000000000000000..4a4dc7cf8e046d8cb0308a3f7819178e7396accf GIT binary patch literal 9007 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 Vap{mr8o&Sm002ovPDHLkV1g%nulN7} literal 0 HcmV?d00001