aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--trunk/ardor3d-examples/src/main/java/com/ardor3d/example/terrain/InMemoryTerrainExample.java2
-rw-r--r--trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTerrainDataProvider.java10
-rw-r--r--trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTerrainDataProvider.java14
-rw-r--r--trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTerrainDataProvider.java9
-rw-r--r--trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/NormalMapUtil.java63
5 files changed, 67 insertions, 31 deletions
diff --git a/trunk/ardor3d-examples/src/main/java/com/ardor3d/example/terrain/InMemoryTerrainExample.java b/trunk/ardor3d-examples/src/main/java/com/ardor3d/example/terrain/InMemoryTerrainExample.java
index 1463e4c..4215b18 100644
--- a/trunk/ardor3d-examples/src/main/java/com/ardor3d/example/terrain/InMemoryTerrainExample.java
+++ b/trunk/ardor3d-examples/src/main/java/com/ardor3d/example/terrain/InMemoryTerrainExample.java
@@ -1 +1 @@
-/** * Copyright (c) 2008-2012 Ardor Labs, Inc. * * This file is part of Ardor3D. * * Ardor3D is free software: you can redistribute it and/or modify it * under the terms of its license which may be found in the accompanying * LICENSE file or at <http://www.ardor3d.com/LICENSE>. */ package com.ardor3d.example.terrain; import com.ardor3d.example.ExampleBase; import com.ardor3d.example.Purpose; import com.ardor3d.extension.terrain.client.Terrain; import com.ardor3d.extension.terrain.client.TerrainBuilder; import com.ardor3d.extension.terrain.client.TerrainDataProvider; import com.ardor3d.extension.terrain.providers.inmemory.InMemoryTerrainDataProvider; import com.ardor3d.extension.terrain.providers.inmemory.data.InMemoryTerrainData; import com.ardor3d.framework.Canvas; import com.ardor3d.image.Texture; import com.ardor3d.input.Key; import com.ardor3d.input.logical.InputTrigger; import com.ardor3d.input.logical.KeyPressedCondition; import com.ardor3d.input.logical.TriggerAction; import com.ardor3d.input.logical.TwoInputStates; import com.ardor3d.intersection.PickingUtil; import com.ardor3d.intersection.PrimitivePickResults; import com.ardor3d.light.DirectionalLight; import com.ardor3d.math.ColorRGBA; import com.ardor3d.math.Ray3; import com.ardor3d.math.Vector3; import com.ardor3d.renderer.Camera; import com.ardor3d.renderer.queue.RenderBucketType; import com.ardor3d.renderer.state.CullState; import com.ardor3d.renderer.state.FogState; import com.ardor3d.renderer.state.FogState.DensityFunction; import com.ardor3d.scenegraph.Node; import com.ardor3d.scenegraph.extension.Skybox; import com.ardor3d.scenegraph.hint.CullHint; import com.ardor3d.scenegraph.hint.LightCombineMode; import com.ardor3d.scenegraph.shape.Sphere; import com.ardor3d.ui.text.BasicText; import com.ardor3d.util.ReadOnlyTimer; import com.ardor3d.util.TextureManager; /** * Example showing the Geometry Clipmap Terrain system with 'MegaTextures' streaming from an in-memory data source. * Requires GLSL support. */ @Purpose(htmlDescriptionKey = "com.ardor3d.example.terrain.InMemoryTerrainExample", // thumbnailPath = "com/ardor3d/example/media/thumbnails/terrain_InMemoryTerrainExample.jpg", // maxHeapMemory = 128) public class InMemoryTerrainExample extends ExampleBase { private boolean updateTerrain = true; private final float farPlane = 8000.0f; private Terrain terrain; private final Sphere sphere = new Sphere("sp", 16, 16, 1); private final Ray3 pickRay = new Ray3(); private boolean groundCamera = false; private Camera terrainCamera; private Skybox skybox; private InMemoryTerrainData inMemoryTerrainData; /** Text fields used to present info about the example. */ private final BasicText _exampleInfo[] = new BasicText[6]; public static void main(final String[] args) { ExampleBase.start(InMemoryTerrainExample.class); } @Override protected void updateExample(final ReadOnlyTimer timer) { final Camera camera = _canvas.getCanvasRenderer().getCamera(); // Make sure camera is above terrain final double height = terrain.getHeightAt(camera.getLocation().getX(), camera.getLocation().getZ()); if (height > -Float.MAX_VALUE && (groundCamera || camera.getLocation().getY() < height + 3)) { camera.setLocation(new Vector3(camera.getLocation().getX(), height + 3, camera.getLocation().getZ())); } if (updateTerrain) { terrainCamera.set(camera); } skybox.setTranslation(camera.getLocation()); // if we're picking... if (sphere.getSceneHints().getCullHint() == CullHint.Dynamic) { // Set up our pick ray pickRay.setOrigin(camera.getLocation()); pickRay.setDirection(camera.getDirection()); // do pick and move the sphere final PrimitivePickResults pickResults = new PrimitivePickResults(); pickResults.setCheckDistance(true); PickingUtil.findPick(_root, pickRay, pickResults); if (pickResults.getNumber() != 0) { final Vector3 intersectionPoint = pickResults.getPickData(0).getIntersectionRecord() .getIntersectionPoint(0); sphere.setTranslation(intersectionPoint); // XXX: maybe change the color of the ball for valid vs. invalid? } } } /** * Initialize pssm pass and scene. */ @Override protected void initExample() { // Setup main camera. _canvas.setTitle("Terrain Example"); _canvas.getCanvasRenderer().getCamera().setLocation(new Vector3(0, 300, 0)); _canvas.getCanvasRenderer().getCamera().lookAt(new Vector3(1, 300, 1), Vector3.UNIT_Y); _canvas.getCanvasRenderer().getCamera().setFrustumPerspective( 70.0, (float) _canvas.getCanvasRenderer().getCamera().getWidth() / _canvas.getCanvasRenderer().getCamera().getHeight(), 1.0f, farPlane); _canvas.getCanvasRenderer().getRenderer().setBackgroundColor(ColorRGBA.GRAY); _controlHandle.setMoveSpeed(200); setupDefaultStates(); sphere.getSceneHints().setAllPickingHints(false); sphere.getSceneHints().setCullHint(CullHint.Always); _root.attachChild(sphere); try { // Keep a separate camera to be able to freeze terrain update final Camera camera = _canvas.getCanvasRenderer().getCamera(); terrainCamera = new Camera(camera); inMemoryTerrainData = new InMemoryTerrainData(2048, 9, 128, new Vector3(1, 200, 1)); final TerrainDataProvider terrainDataProvider = new InMemoryTerrainDataProvider(inMemoryTerrainData); terrain = new TerrainBuilder(terrainDataProvider, terrainCamera).setShowDebugPanels(true).build(); _root.attachChild(terrain); } catch (final Exception ex1) { System.out.println("Problem setting up terrain..."); ex1.printStackTrace(); } skybox = buildSkyBox(); skybox.getSceneHints().setAllPickingHints(false); _root.attachChild(skybox); // Setup labels for presenting example info. final Node textNodes = new Node("Text"); _root.attachChild(textNodes); textNodes.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); textNodes.getSceneHints().setLightCombineMode(LightCombineMode.Off); final double infoStartY = _canvas.getCanvasRenderer().getCamera().getHeight() / 2; for (int i = 0; i < _exampleInfo.length; i++) { _exampleInfo[i] = BasicText.createDefaultTextLabel("Text", "", 16); _exampleInfo[i].setTranslation(new Vector3(10, infoStartY - i * 20, 0)); textNodes.attachChild(_exampleInfo[i]); } textNodes.updateGeometricState(0.0); updateText(); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.V), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { if (!inMemoryTerrainData.isRunning()) { inMemoryTerrainData.startUpdates(); } else { inMemoryTerrainData.stopUpdates(); } updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.U), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { updateTerrain = !updateTerrain; updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ONE), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { _controlHandle.setMoveSpeed(5); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.TWO), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { _controlHandle.setMoveSpeed(50); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.THREE), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { _controlHandle.setMoveSpeed(400); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FOUR), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { _controlHandle.setMoveSpeed(1000); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SPACE), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { groundCamera = !groundCamera; updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.P), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { if (sphere.getSceneHints().getCullHint() == CullHint.Dynamic) { sphere.getSceneHints().setCullHint(CullHint.Always); } else if (sphere.getSceneHints().getCullHint() == CullHint.Always) { sphere.getSceneHints().setCullHint(CullHint.Dynamic); } updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.R), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { terrain.getTextureClipmap().setShowDebug(!terrain.getTextureClipmap().isShowDebug()); terrain.reloadShader(); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.G), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { terrain.reloadShader(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FIVE), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { terrain.getTextureClipmap().setScale(terrain.getTextureClipmap().getScale() / 2); terrain.reloadShader(); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SIX), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { terrain.getTextureClipmap().setScale(terrain.getTextureClipmap().getScale() * 2); terrain.reloadShader(); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SEVEN), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { final Camera camera = _canvas.getCanvasRenderer().getCamera(); camera.setLocation(camera.getLocation().getX() + 500.0, camera.getLocation().getY(), camera .getLocation().getZ()); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.EIGHT), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { final Camera camera = _canvas.getCanvasRenderer().getCamera(); camera.setLocation(camera.getLocation().getX() - 500.0, camera.getLocation().getY(), camera .getLocation().getZ()); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.NINE), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { final Camera camera = _canvas.getCanvasRenderer().getCamera(); camera.setLocation(camera.getLocation().getX(), camera.getLocation().getY(), camera.getLocation() .getZ() + 1500.0); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ZERO), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { final Camera camera = _canvas.getCanvasRenderer().getCamera(); camera.setLocation(camera.getLocation().getX(), camera.getLocation().getY(), camera.getLocation() .getZ() - 1500.0); } })); } private void setupDefaultStates() { _lightState.detachAll(); final DirectionalLight dLight = new DirectionalLight(); dLight.setEnabled(true); dLight.setAmbient(new ColorRGBA(0.4f, 0.4f, 0.5f, 1)); dLight.setDiffuse(new ColorRGBA(0.6f, 0.6f, 0.5f, 1)); dLight.setSpecular(new ColorRGBA(0.3f, 0.3f, 0.2f, 1)); dLight.setDirection(new Vector3(-1, -1, -1).normalizeLocal()); _lightState.attach(dLight); _lightState.setEnabled(true); final CullState cs = new CullState(); cs.setEnabled(true); cs.setCullFace(CullState.Face.Back); _root.setRenderState(cs); final FogState fs = new FogState(); fs.setStart(farPlane / 2.0f); fs.setEnd(farPlane); fs.setColor(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f)); fs.setDensityFunction(DensityFunction.Linear); _root.setRenderState(fs); } /** * Builds the sky box. */ private Skybox buildSkyBox() { final Skybox skybox = new Skybox("skybox", 10, 10, 10); final String dir = "images/skybox/"; final Texture north = TextureManager .load(dir + "1.jpg", Texture.MinificationFilter.BilinearNearestMipMap, true); final Texture south = TextureManager .load(dir + "3.jpg", Texture.MinificationFilter.BilinearNearestMipMap, true); final Texture east = TextureManager.load(dir + "2.jpg", Texture.MinificationFilter.BilinearNearestMipMap, true); final Texture west = TextureManager.load(dir + "4.jpg", Texture.MinificationFilter.BilinearNearestMipMap, true); final Texture up = TextureManager.load(dir + "6.jpg", Texture.MinificationFilter.BilinearNearestMipMap, true); final Texture down = TextureManager.load(dir + "5.jpg", Texture.MinificationFilter.BilinearNearestMipMap, true); skybox.setTexture(Skybox.Face.North, north); skybox.setTexture(Skybox.Face.West, west); skybox.setTexture(Skybox.Face.South, south); skybox.setTexture(Skybox.Face.East, east); skybox.setTexture(Skybox.Face.Up, up); skybox.setTexture(Skybox.Face.Down, down); return skybox; } /** * Update text information. */ private void updateText() { _exampleInfo[0].setText("[1/2/3] Moving speed: " + _controlHandle.getMoveSpeed() * 3.6 + " km/h"); _exampleInfo[1].setText("[P] Do picking: " + (sphere.getSceneHints().getCullHint() == CullHint.Dynamic)); _exampleInfo[2].setText("[SPACE] Toggle fly/walk: " + (groundCamera ? "walk" : "fly")); _exampleInfo[3].setText("[J] Regenerate heightmap/texture"); _exampleInfo[4].setText("[U] Freeze terrain(debug): " + !updateTerrain); _exampleInfo[5].setText("[V] Updating terrain data: " + inMemoryTerrainData.isRunning()); } } \ No newline at end of file
+/** * Copyright (c) 2008-2012 Ardor Labs, Inc. * * This file is part of Ardor3D. * * Ardor3D is free software: you can redistribute it and/or modify it * under the terms of its license which may be found in the accompanying * LICENSE file or at <http://www.ardor3d.com/LICENSE>. */ package com.ardor3d.example.terrain; import com.ardor3d.example.ExampleBase; import com.ardor3d.example.Purpose; import com.ardor3d.extension.terrain.client.Terrain; import com.ardor3d.extension.terrain.client.TerrainBuilder; import com.ardor3d.extension.terrain.client.TerrainDataProvider; import com.ardor3d.extension.terrain.providers.inmemory.InMemoryTerrainDataProvider; import com.ardor3d.extension.terrain.providers.inmemory.data.InMemoryTerrainData; import com.ardor3d.framework.Canvas; import com.ardor3d.image.Texture; import com.ardor3d.input.Key; import com.ardor3d.input.logical.InputTrigger; import com.ardor3d.input.logical.KeyPressedCondition; import com.ardor3d.input.logical.TriggerAction; import com.ardor3d.input.logical.TwoInputStates; import com.ardor3d.intersection.PickingUtil; import com.ardor3d.intersection.PrimitivePickResults; import com.ardor3d.light.DirectionalLight; import com.ardor3d.math.ColorRGBA; import com.ardor3d.math.Ray3; import com.ardor3d.math.Vector3; import com.ardor3d.renderer.Camera; import com.ardor3d.renderer.queue.RenderBucketType; import com.ardor3d.renderer.state.CullState; import com.ardor3d.renderer.state.FogState; import com.ardor3d.renderer.state.FogState.DensityFunction; import com.ardor3d.scenegraph.Node; import com.ardor3d.scenegraph.extension.Skybox; import com.ardor3d.scenegraph.hint.CullHint; import com.ardor3d.scenegraph.hint.LightCombineMode; import com.ardor3d.scenegraph.shape.Sphere; import com.ardor3d.ui.text.BasicText; import com.ardor3d.util.ReadOnlyTimer; import com.ardor3d.util.TextureManager; /** * Example showing the Geometry Clipmap Terrain system with 'MegaTextures' streaming from an in-memory data source. * Requires GLSL support. */ @Purpose(htmlDescriptionKey = "com.ardor3d.example.terrain.InMemoryTerrainExample", // thumbnailPath = "com/ardor3d/example/media/thumbnails/terrain_InMemoryTerrainExample.jpg", // maxHeapMemory = 128) public class InMemoryTerrainExample extends ExampleBase { private boolean updateTerrain = true; private final float farPlane = 8000.0f; private Terrain terrain; private final Sphere sphere = new Sphere("sp", 16, 16, 1); private final Ray3 pickRay = new Ray3(); private boolean groundCamera = false; private Camera terrainCamera; private Skybox skybox; private InMemoryTerrainData inMemoryTerrainData; /** Text fields used to present info about the example. */ private final BasicText _exampleInfo[] = new BasicText[6]; public static void main(final String[] args) { ExampleBase.start(InMemoryTerrainExample.class); } @Override protected void updateExample(final ReadOnlyTimer timer) { final Camera camera = _canvas.getCanvasRenderer().getCamera(); // Make sure camera is above terrain final double height = terrain.getHeightAt(camera.getLocation().getX(), camera.getLocation().getZ()); if (height > -Float.MAX_VALUE && (groundCamera || camera.getLocation().getY() < height + 3)) { camera.setLocation(new Vector3(camera.getLocation().getX(), height + 3, camera.getLocation().getZ())); } if (updateTerrain) { terrainCamera.set(camera); } skybox.setTranslation(camera.getLocation()); // if we're picking... if (sphere.getSceneHints().getCullHint() == CullHint.Dynamic) { // Set up our pick ray pickRay.setOrigin(camera.getLocation()); pickRay.setDirection(camera.getDirection()); // do pick and move the sphere final PrimitivePickResults pickResults = new PrimitivePickResults(); pickResults.setCheckDistance(true); PickingUtil.findPick(_root, pickRay, pickResults); if (pickResults.getNumber() != 0) { final Vector3 intersectionPoint = pickResults.getPickData(0).getIntersectionRecord() .getIntersectionPoint(0); sphere.setTranslation(intersectionPoint); // XXX: maybe change the color of the ball for valid vs. invalid? } } } /** * Initialize pssm pass and scene. */ @Override protected void initExample() { // Setup main camera. _canvas.setTitle("Terrain Example"); _canvas.getCanvasRenderer().getCamera().setLocation(new Vector3(0, 300, 0)); _canvas.getCanvasRenderer().getCamera().lookAt(new Vector3(1, 300, 1), Vector3.UNIT_Y); _canvas.getCanvasRenderer() .getCamera() .setFrustumPerspective( 70.0, (float) _canvas.getCanvasRenderer().getCamera().getWidth() / _canvas.getCanvasRenderer().getCamera().getHeight(), 1.0f, farPlane); _canvas.getCanvasRenderer().getRenderer().setBackgroundColor(ColorRGBA.GRAY); _controlHandle.setMoveSpeed(200); setupDefaultStates(); sphere.getSceneHints().setAllPickingHints(false); sphere.getSceneHints().setCullHint(CullHint.Always); _root.attachChild(sphere); try { // Keep a separate camera to be able to freeze terrain update final Camera camera = _canvas.getCanvasRenderer().getCamera(); terrainCamera = new Camera(camera); inMemoryTerrainData = new InMemoryTerrainData(2048, 9, 128, new Vector3(1, 200, 1)); final TerrainDataProvider terrainDataProvider = new InMemoryTerrainDataProvider(inMemoryTerrainData, true); terrain = new TerrainBuilder(terrainDataProvider, terrainCamera).setShowDebugPanels(true).build(); _root.attachChild(terrain); } catch (final Exception ex1) { System.out.println("Problem setting up terrain..."); ex1.printStackTrace(); } skybox = buildSkyBox(); skybox.getSceneHints().setAllPickingHints(false); _root.attachChild(skybox); // Setup labels for presenting example info. final Node textNodes = new Node("Text"); _root.attachChild(textNodes); textNodes.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); textNodes.getSceneHints().setLightCombineMode(LightCombineMode.Off); final double infoStartY = _canvas.getCanvasRenderer().getCamera().getHeight() / 2; for (int i = 0; i < _exampleInfo.length; i++) { _exampleInfo[i] = BasicText.createDefaultTextLabel("Text", "", 16); _exampleInfo[i].setTranslation(new Vector3(10, infoStartY - i * 20, 0)); textNodes.attachChild(_exampleInfo[i]); } textNodes.updateGeometricState(0.0); updateText(); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.V), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { if (!inMemoryTerrainData.isRunning()) { inMemoryTerrainData.startUpdates(); } else { inMemoryTerrainData.stopUpdates(); } updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.U), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { updateTerrain = !updateTerrain; updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ONE), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { _controlHandle.setMoveSpeed(5); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.TWO), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { _controlHandle.setMoveSpeed(50); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.THREE), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { _controlHandle.setMoveSpeed(400); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FOUR), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { _controlHandle.setMoveSpeed(1000); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SPACE), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { groundCamera = !groundCamera; updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.P), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { if (sphere.getSceneHints().getCullHint() == CullHint.Dynamic) { sphere.getSceneHints().setCullHint(CullHint.Always); } else if (sphere.getSceneHints().getCullHint() == CullHint.Always) { sphere.getSceneHints().setCullHint(CullHint.Dynamic); } updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.R), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { terrain.getTextureClipmap().setShowDebug(!terrain.getTextureClipmap().isShowDebug()); terrain.reloadShader(); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.G), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { terrain.reloadShader(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FIVE), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { terrain.getTextureClipmap().setScale(terrain.getTextureClipmap().getScale() / 2); terrain.reloadShader(); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SIX), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { terrain.getTextureClipmap().setScale(terrain.getTextureClipmap().getScale() * 2); terrain.reloadShader(); updateText(); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SEVEN), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { final Camera camera = _canvas.getCanvasRenderer().getCamera(); camera.setLocation(camera.getLocation().getX() + 500.0, camera.getLocation().getY(), camera .getLocation().getZ()); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.EIGHT), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { final Camera camera = _canvas.getCanvasRenderer().getCamera(); camera.setLocation(camera.getLocation().getX() - 500.0, camera.getLocation().getY(), camera .getLocation().getZ()); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.NINE), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { final Camera camera = _canvas.getCanvasRenderer().getCamera(); camera.setLocation(camera.getLocation().getX(), camera.getLocation().getY(), camera.getLocation() .getZ() + 1500.0); } })); _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ZERO), new TriggerAction() { public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { final Camera camera = _canvas.getCanvasRenderer().getCamera(); camera.setLocation(camera.getLocation().getX(), camera.getLocation().getY(), camera.getLocation() .getZ() - 1500.0); } })); } private void setupDefaultStates() { _lightState.detachAll(); final DirectionalLight dLight = new DirectionalLight(); dLight.setEnabled(true); dLight.setAmbient(new ColorRGBA(0.4f, 0.4f, 0.5f, 1)); dLight.setDiffuse(new ColorRGBA(0.6f, 0.6f, 0.5f, 1)); dLight.setSpecular(new ColorRGBA(0.3f, 0.3f, 0.2f, 1)); dLight.setDirection(new Vector3(-1, -1, -1).normalizeLocal()); _lightState.attach(dLight); _lightState.setEnabled(true); final CullState cs = new CullState(); cs.setEnabled(true); cs.setCullFace(CullState.Face.Back); _root.setRenderState(cs); final FogState fs = new FogState(); fs.setStart(farPlane / 2.0f); fs.setEnd(farPlane); fs.setColor(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f)); fs.setDensityFunction(DensityFunction.Linear); _root.setRenderState(fs); } /** * Builds the sky box. */ private Skybox buildSkyBox() { final Skybox skybox = new Skybox("skybox", 10, 10, 10); final String dir = "images/skybox/"; final Texture north = TextureManager .load(dir + "1.jpg", Texture.MinificationFilter.BilinearNearestMipMap, true); final Texture south = TextureManager .load(dir + "3.jpg", Texture.MinificationFilter.BilinearNearestMipMap, true); final Texture east = TextureManager.load(dir + "2.jpg", Texture.MinificationFilter.BilinearNearestMipMap, true); final Texture west = TextureManager.load(dir + "4.jpg", Texture.MinificationFilter.BilinearNearestMipMap, true); final Texture up = TextureManager.load(dir + "6.jpg", Texture.MinificationFilter.BilinearNearestMipMap, true); final Texture down = TextureManager.load(dir + "5.jpg", Texture.MinificationFilter.BilinearNearestMipMap, true); skybox.setTexture(Skybox.Face.North, north); skybox.setTexture(Skybox.Face.West, west); skybox.setTexture(Skybox.Face.South, south); skybox.setTexture(Skybox.Face.East, east); skybox.setTexture(Skybox.Face.Up, up); skybox.setTexture(Skybox.Face.Down, down); return skybox; } /** * Update text information. */ private void updateText() { _exampleInfo[0].setText("[1/2/3] Moving speed: " + _controlHandle.getMoveSpeed() * 3.6 + " km/h"); _exampleInfo[1].setText("[P] Do picking: " + (sphere.getSceneHints().getCullHint() == CullHint.Dynamic)); _exampleInfo[2].setText("[SPACE] Toggle fly/walk: " + (groundCamera ? "walk" : "fly")); _exampleInfo[3].setText("[J] Regenerate heightmap/texture"); _exampleInfo[4].setText("[U] Freeze terrain(debug): " + !updateTerrain); _exampleInfo[5].setText("[V] Updating terrain data: " + inMemoryTerrainData.isRunning()); } } \ No newline at end of file
diff --git a/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTerrainDataProvider.java b/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTerrainDataProvider.java
index 2664abd..3e3b6ae 100644
--- a/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTerrainDataProvider.java
+++ b/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTerrainDataProvider.java
@@ -19,10 +19,8 @@ import com.ardor3d.extension.terrain.client.TerrainSource;
import com.ardor3d.extension.terrain.client.TextureSource;
import com.ardor3d.extension.terrain.providers.image.ImageTextureSource;
import com.ardor3d.extension.terrain.util.NormalMapUtil;
-import com.ardor3d.image.Texture;
-import com.ardor3d.image.Texture.MinificationFilter;
+import com.ardor3d.image.Image;
import com.ardor3d.math.type.ReadOnlyVector3;
-import com.ardor3d.util.TextureManager;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -97,9 +95,9 @@ public class ArrayTerrainDataProvider implements TerrainDataProvider {
try {
final float[] data = heightMaps.get(heightMaps.size() - 1);
final int size = heightMapSizes.get(heightMapSizes.size() - 1);
- final Texture normal = TextureManager.loadFromImage(NormalMapUtil.constructNormalMap(data,
- scale.getX(), scale.getY(), getHeightMax(), size), MinificationFilter.BilinearNoMipMaps);
- return new ImageTextureSource(tileSize, normal.getImage(), heightMapSizes);
+ final Image normalImage = NormalMapUtil.constructNormalMap(data, size, scale.getY() / heightMax,
+ scale.getX(), scale.getZ());
+ return new ImageTextureSource(tileSize, normalImage, heightMapSizes);
} catch (final Exception e) {
e.printStackTrace();
}
diff --git a/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTerrainDataProvider.java b/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTerrainDataProvider.java
index e8ae40e..e737243 100644
--- a/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTerrainDataProvider.java
+++ b/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTerrainDataProvider.java
@@ -19,9 +19,7 @@ import com.ardor3d.extension.terrain.client.TextureSource;
import com.ardor3d.extension.terrain.providers.image.ImageTextureSource;
import com.ardor3d.extension.terrain.providers.inmemory.data.InMemoryTerrainData;
import com.ardor3d.extension.terrain.util.NormalMapUtil;
-import com.ardor3d.image.Texture;
-import com.ardor3d.image.Texture.MinificationFilter;
-import com.ardor3d.util.TextureManager;
+import com.ardor3d.image.Image;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -62,10 +60,10 @@ public class InMemoryTerrainDataProvider implements TerrainDataProvider {
public TextureSource getNormalMapSource(final int mapId) {
if (generateNormalMap) {
try {
- final Texture normal = TextureManager.loadFromImage(NormalMapUtil.constructNormalMap(
- inMemoryTerrainData.getHeightData(), inMemoryTerrainData.getScale().getX(), inMemoryTerrainData
- .getScale().getY(), inMemoryTerrainData.getMaxHeight(), inMemoryTerrainData.getSide()),
- MinificationFilter.BilinearNoMipMaps);
+ final Image normalImage = NormalMapUtil.constructNormalMap(inMemoryTerrainData.getHeightData(),
+ inMemoryTerrainData.getSide(), inMemoryTerrainData.getMaxHeight(), inMemoryTerrainData
+ .getScale().getX(), inMemoryTerrainData.getScale().getY());
+
final List<Integer> heightMapSizes = Lists.newArrayList();
int currentSize = inMemoryTerrainData.getSide();
heightMapSizes.add(currentSize);
@@ -73,7 +71,7 @@ public class InMemoryTerrainDataProvider implements TerrainDataProvider {
currentSize /= 2;
heightMapSizes.add(currentSize);
}
- return new ImageTextureSource(tileSize, normal.getImage(), heightMapSizes);
+ return new ImageTextureSource(tileSize, normalImage, heightMapSizes);
} catch (final Exception e) {
e.printStackTrace();
}
diff --git a/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTerrainDataProvider.java b/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTerrainDataProvider.java
index 888838c..3d7878b 100644
--- a/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTerrainDataProvider.java
+++ b/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTerrainDataProvider.java
@@ -18,9 +18,7 @@ import com.ardor3d.extension.terrain.client.TerrainSource;
import com.ardor3d.extension.terrain.client.TextureSource;
import com.ardor3d.extension.terrain.providers.image.ImageTextureSource;
import com.ardor3d.extension.terrain.util.NormalMapUtil;
-import com.ardor3d.image.Texture;
-import com.ardor3d.image.Texture.MinificationFilter;
-import com.ardor3d.util.TextureManager;
+import com.ardor3d.image.Image;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -67,8 +65,7 @@ public class SimpleArrayTerrainDataProvider implements TerrainDataProvider {
public TextureSource getNormalMapSource(final int mapId) {
if (generateNormalMap) {
try {
- final Texture normal = TextureManager.loadFromImage(NormalMapUtil.constructNormalMap(heightData, 1, 1,
- 1, side), MinificationFilter.BilinearNoMipMaps);
+ final Image normalImage = NormalMapUtil.constructNormalMap(heightData, side, 1, 1, 1);
final List<Integer> heightMapSizes = Lists.newArrayList();
int currentSize = side;
heightMapSizes.add(currentSize);
@@ -76,7 +73,7 @@ public class SimpleArrayTerrainDataProvider implements TerrainDataProvider {
currentSize /= 2;
heightMapSizes.add(currentSize);
}
- return new ImageTextureSource(tileSize, normal.getImage(), heightMapSizes);
+ return new ImageTextureSource(tileSize, normalImage, heightMapSizes);
} catch (final Exception e) {
e.printStackTrace();
}
diff --git a/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/NormalMapUtil.java b/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/NormalMapUtil.java
index 53df22d..89e012b 100644
--- a/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/NormalMapUtil.java
+++ b/trunk/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/NormalMapUtil.java
@@ -19,24 +19,67 @@ import com.ardor3d.math.Vector3;
public class NormalMapUtil {
- public static Image constructNormalMap(final float[] heightmap, final double spacing, final double zScale,
- final double mapScale, final int side) {
+ /**
+ * Generate an image from the given terrain height data to be used as a source for terrain normal maps.
+ *
+ * @param heightmap
+ * the base height data. Generally this is the most detailed height data available. It must be a square
+ * heightmap, with a side of "side" as passed below.
+ * @param side
+ * the number of samples on a side of the heightmap. This could be calculated by taking the squareroot of
+ * heightmap.length, but generally this number is well known by the caller.
+ * @param heightScale
+ * the scaling factor applied to the heightMap values to get real world height.
+ * @param xGridSpacing
+ * real world spacing between grid in the x direction
+ * @param zGridSpacing
+ * real world spacing between grid in the z direction
+ * @return the normal image.
+ */
+ public static Image constructNormalMap(final float[] heightmap, final int side, final double heightScale,
+ final double xGridSpacing, final double zGridSpacing) {
int x, z;
final Vector3 n = new Vector3();
+ final Vector3 n2 = new Vector3();
final ByteBuffer data = ByteBuffer.allocateDirect(side * side * 3);
final Image normalMap = new Image(ImageDataFormat.RGB, PixelDataType.UnsignedByte, side, side, data, null);
for (z = 0; z < side; ++z) {
for (x = 0; x < side; ++x) {
- n.setZ(1);
if (x == 0 || z == 0 || x == side - 1 || z == side - 1) {
- n.setX(0);
- n.setY(0);
+ n.set(0, 0, 1);
} else {
- n.setX(zScale * (heightmap[z * side + x - 1] - heightmap[z * side + x + 1]) / 2
- / (mapScale * spacing));
- n.setY(zScale * (heightmap[(z - 1) * side + x] - heightmap[(z + 1) * side + x]) / 2
- / (mapScale * spacing));
- n.normalizeLocal();
+ // change across "x" from point to our "left" to point on our "right"
+ double dXh = heightmap[z * side + x - 1] - heightmap[z * side + x + 1];
+ if (dXh != 0) {
+ // alter by our height scale
+ dXh *= heightScale;
+ // determine slope of perpendicular line
+ final double slopeX = 2.0 * xGridSpacing / dXh;
+ // now plug into cos(arctan(x)) to get unit length vector
+ n.setX(Math.copySign(1.0 / Math.sqrt(1 + slopeX * slopeX), dXh));
+ n.setY(0);
+ n.setZ(Math.abs(slopeX * n.getX()));
+ } else {
+ n.set(0, 0, 1);
+ }
+
+ // change across "z" from point "above" us to point "below" us
+ double dZh = heightmap[(z - 1) * side + x] - heightmap[(z + 1) * side + x];
+ if (dZh != 0) {
+ // alter by our height scale
+ dZh *= heightScale;
+ // determine slope of perpendicular line
+ final double slopeZ = 2.0 * zGridSpacing / dZh;
+ // now plug into cos(arctan(x)) to get unit length vector
+ n2.setX(0);
+ n2.setY(Math.copySign(1.0 / Math.sqrt(1 + slopeZ * slopeZ), dZh));
+ n2.setZ(Math.abs(slopeZ * n2.getY()));
+ } else {
+ n2.set(0, 0, 1);
+ }
+
+ // add together the vectors across X and Z and normalize to get final normal
+ n.addLocal(n2).normalizeLocal();
}
// System.err.println(n);
data.put(3 * (z * side + x) + 0, (byte) ((int) (127 * n.getX()) + 128));