Browse Source

Added my own noise library as jar (noise-0.0.1-SNAPSHOT.jar), hope I didn't break any build stuff
TerrainGridTest is running smooth now using 257x257 sized TerrainQuads with fractal based heightmaps calculated on the fly.

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7529 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

ant..om 14 years ago
parent
commit
58ede837c6

+ 2 - 0
engine/lib/nblibraries.properties

@@ -54,4 +54,6 @@ libs.swing-layout.javadoc=\
     ${base}/swing-layout/swing-layout-1.0.4-doc.zip
 libs.swing-layout.src=\
     ${base}/swing-layout/swing-layout-1.0.4-src.zip
+libs.noise.classpath=\
+    ${base}/noise/noise-0.0.1-SNAPSHOT.jar
 

BIN
engine/lib/noise/noise-0.0.1-SNAPSHOT.jar


+ 113 - 112
engine/nbproject/project.properties

@@ -1,112 +1,113 @@
-annotation.processing.enabled=false
-annotation.processing.enabled.in.editor=false
-annotation.processing.run.all.processors=true
-ant.customtasks.libs=JWSAntTasks
-application.homepage=http://www.jmonkeyengine.com/
-application.title=jMonkeyEngine 3.0
-application.vendor=jMonkeyEngine
-build.classes.dir=${build.dir}/classes
-build.classes.excludes=**/*.java,**/*.form
-# This directory is removed when the project is cleaned:
-build.dir=build
-build.generated.dir=${build.dir}/generated
-build.generated.sources.dir=${build.dir}/generated-sources
-# Only compile against the classpath explicitly listed here:
-build.sysclasspath=ignore
-build.test.classes.dir=${build.dir}/test/classes
-build.test.results.dir=${build.dir}/test/results
-# Uncomment to specify the preferred debugger connection transport:
-#debug.transport=dt_socket
-debug.classpath=\
-    ${run.classpath}
-debug.test.classpath=\
-    ${run.test.classpath}
-# This directory is removed when the project is cleaned:
-dist.dir=dist
-dist.jar=${dist.dir}/jMonkeyEngine3.jar
-dist.javadoc.dir=${dist.dir}/javadoc
-endorsed.classpath=
-excludes=
-file.reference.src-test-data=src/test-data
-includes=**
-jar.archive.disabled=${jnlp.enabled}
-jar.compress=true
-jar.index=${jnlp.enabled}
-javac.classpath=\
-    ${libs.jogg.classpath}:\
-    ${libs.jbullet.classpath}:\
-    ${libs.bullet.classpath}:\
-    ${libs.lwjgl.classpath}:\
-    ${libs.jheora.classpath}:\
-    ${libs.niftygui1.3.classpath}:\
-    ${libs.jme3-test-data.classpath}
-# Space-separated list of extra javac options
-javac.compilerargs=
-javac.deprecation=false
-javac.processorpath=\
-    ${javac.classpath}
-javac.source=1.5
-javac.target=1.5
-javac.test.classpath=\
-    ${javac.classpath}:\
-    ${build.classes.dir}:\
-    ${libs.junit_4.classpath}
-javadoc.additionalparam=
-javadoc.author=false
-javadoc.encoding=${source.encoding}
-javadoc.noindex=false
-javadoc.nonavbar=false
-javadoc.notree=false
-javadoc.private=false
-javadoc.splitindex=true
-javadoc.use=true
-javadoc.version=false
-javadoc.windowtitle=jMonkeyEngine3
-jaxbwiz.endorsed.dirs="${netbeans.home}/../ide12/modules/ext/jaxb/api"
-jnlp.applet.class=jme3test.awt.AppHarness
-jnlp.applet.height=300
-jnlp.applet.width=300
-jnlp.codebase.type=user
-jnlp.codebase.user=http://jmonkeyengine.com/javawebstart/
-jnlp.descriptor=application
-jnlp.enabled=false
-jnlp.icon=/Users/normenhansen/Pictures/jme/icons/jme-logo48.png
-jnlp.mixed.code=default
-jnlp.offline-allowed=true
-jnlp.signed=true
-jnlp.signing=generated
-jnlp.signing.alias=
-jnlp.signing.keystore=
-main.class=jme3test.TestChooser
-manifest.file=MANIFEST.MF
-meta.inf.dir=${src.dir}/META-INF
-mkdist.disabled=false
-platform.active=default_platform
-run.classpath=\
-    ${javac.classpath}:\
-    ${build.classes.dir}
-run.jvmargs=-Xms40m -Xmx40m -XX:MaxDirectMemorySize=256M
-run.test.classpath=\
-    ${javac.test.classpath}:\
-    ${build.test.classes.dir}
-source.encoding=UTF-8
-src.core-data.dir=src/core-data
-src.core-plugins.dir=src/core-plugins
-src.core.dir=src/core
-src.desktop-fx.dir=src/desktop-fx
-src.desktop.dir=src/desktop
-src.games.dir=src/games
-src.jbullet.dir=src/jbullet
-src.jheora.dir=src/jheora
-src.jogg.dir=src/jogg
-src.lwjgl-oal.dir=src/lwjgl-oal
-src.lwjgl-ogl.dir=src/lwjgl-ogl
-src.networking.dir=src\\networking
-src.niftygui.dir=src/niftygui
-src.ogre.dir=src/ogre
-src.pack.dir=src/pack
-src.terrain.dir=src/terrain
-src.test.dir=src/test
-src.tools.dir=src/tools
-src.xml.dir=src/xml
-test.test.dir=test
+annotation.processing.enabled=false
+annotation.processing.enabled.in.editor=false
+annotation.processing.run.all.processors=true
+ant.customtasks.libs=JWSAntTasks
+application.homepage=http://www.jmonkeyengine.com/
+application.title=jMonkeyEngine 3.0
+application.vendor=jMonkeyEngine
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+build.generated.sources.dir=${build.dir}/generated-sources
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+# Uncomment to specify the preferred debugger connection transport:
+#debug.transport=dt_socket
+debug.classpath=\
+    ${run.classpath}
+debug.test.classpath=\
+    ${run.test.classpath}
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/jMonkeyEngine3.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+endorsed.classpath=
+excludes=
+file.reference.src-test-data=src/test-data
+includes=**
+jar.archive.disabled=${jnlp.enabled}
+jar.compress=true
+jar.index=${jnlp.enabled}
+javac.classpath=\
+    ${libs.jogg.classpath}:\
+    ${libs.jbullet.classpath}:\
+    ${libs.bullet.classpath}:\
+    ${libs.lwjgl.classpath}:\
+    ${libs.jheora.classpath}:\
+    ${libs.niftygui1.3.classpath}:\
+    ${libs.jme3-test-data.classpath}:\
+    ${libs.noise.classpath}
+# Space-separated list of extra javac options
+javac.compilerargs=
+javac.deprecation=false
+javac.processorpath=\
+    ${javac.classpath}
+javac.source=1.5
+javac.target=1.5
+javac.test.classpath=\
+    ${javac.classpath}:\
+    ${build.classes.dir}:\
+    ${libs.junit_4.classpath}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=${source.encoding}
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=jMonkeyEngine3
+jaxbwiz.endorsed.dirs="${netbeans.home}/../ide12/modules/ext/jaxb/api"
+jnlp.applet.class=jme3test.awt.AppHarness
+jnlp.applet.height=300
+jnlp.applet.width=300
+jnlp.codebase.type=user
+jnlp.codebase.user=http://jmonkeyengine.com/javawebstart/
+jnlp.descriptor=application
+jnlp.enabled=false
+jnlp.icon=/Users/normenhansen/Pictures/jme/icons/jme-logo48.png
+jnlp.mixed.code=default
+jnlp.offline-allowed=true
+jnlp.signed=true
+jnlp.signing=generated
+jnlp.signing.alias=
+jnlp.signing.keystore=
+main.class=jme3test.TestChooser
+manifest.file=MANIFEST.MF
+meta.inf.dir=${src.dir}/META-INF
+mkdist.disabled=false
+platform.active=default_platform
+run.classpath=\
+    ${javac.classpath}:\
+    ${build.classes.dir}
+run.jvmargs=-Xms40m -Xmx40m -XX:MaxDirectMemorySize=256M
+run.test.classpath=\
+    ${javac.test.classpath}:\
+    ${build.test.classes.dir}
+source.encoding=UTF-8
+src.core-data.dir=src/core-data
+src.core-plugins.dir=src/core-plugins
+src.core.dir=src/core
+src.desktop-fx.dir=src/desktop-fx
+src.desktop.dir=src/desktop
+src.games.dir=src/games
+src.jbullet.dir=src/jbullet
+src.jheora.dir=src/jheora
+src.jogg.dir=src/jogg
+src.lwjgl-oal.dir=src/lwjgl-oal
+src.lwjgl-ogl.dir=src/lwjgl-ogl
+src.networking.dir=src\\networking
+src.niftygui.dir=src/niftygui
+src.ogre.dir=src/ogre
+src.pack.dir=src/pack
+src.terrain.dir=src/terrain
+src.test.dir=src/test
+src.tools.dir=src/tools
+src.xml.dir=src/xml
+test.test.dir=test

+ 62 - 0
engine/src/terrain/com/jme3/terrain/MapUtils.java

@@ -0,0 +1,62 @@
+package com.jme3.terrain;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.logging.Logger;
+
+import javax.imageio.ImageIO;
+
+import org.novyon.noise.ShaderUtils;
+
+public class MapUtils {
+
+	public static FloatBuffer clip(FloatBuffer src, int origSize, int newSize, int offset) {
+		FloatBuffer result = FloatBuffer.allocate(newSize * newSize);
+
+		float[] orig = src.array();
+		for (int i = offset; i < offset + newSize; i++) {
+			result.put(orig, i * origSize + offset, newSize);
+		}
+
+		return result;
+	}
+
+	public static BufferedImage toGrayscale16Image(FloatBuffer buff, int size) {
+		BufferedImage retval = new BufferedImage(size, size, BufferedImage.TYPE_USHORT_GRAY);
+		buff.rewind();
+		for (int y = 0; y < size; y++) {
+			for (int x = 0; x < size; x++) {
+				short c = (short) (ShaderUtils.clamp(buff.get(), 0, 1) * 65532);
+				retval.getRaster().setDataElements(x, y, new short[] { c });
+			}
+		}
+		return retval;
+	}
+
+	public static BufferedImage toGrayscaleRGBImage(FloatBuffer buff, int size) {
+		BufferedImage retval = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
+		buff.rewind();
+		for (int y = 0; y < size; y++) {
+			for (int x = 0; x < size; x++) {
+				int c = (int) (ShaderUtils.clamp(buff.get(), 0, 1) * 255);
+				retval.setRGB(x, y, 0xFF000000 | c << 16 | c << 8 | c);
+			}
+		}
+		return retval;
+	}
+
+	public static void saveImage(BufferedImage im, String file) {
+		MapUtils.saveImage(im, new File(file));
+	}
+
+	public static void saveImage(BufferedImage im, File file) {
+		try {
+			ImageIO.write(im, "PNG", file);
+			Logger.getLogger(MapUtils.class.getCanonicalName()).info("Saved image as : " + file.getAbsolutePath());
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+}

+ 124 - 0
engine/src/terrain/com/jme3/terrain/geomipmap/LRUCache.java

@@ -0,0 +1,124 @@
+package com.jme3.terrain.geomipmap;
+
+// Copyright 2007 Christian d'Heureuse, Inventec Informatik AG, Zurich,
+// Switzerland
+// www.source-code.biz, www.inventec.ch/chdh
+//
+// This module is multi-licensed and may be used under the terms
+// of any of the following licenses:
+//
+// EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal
+// LGPL, GNU Lesser General Public License, V2 or later,
+// http://www.gnu.org/licenses/lgpl.html
+// GPL, GNU General Public License, V2 or later,
+// http://www.gnu.org/licenses/gpl.html
+// AL, Apache License, V2.0 or later, http://www.apache.org/licenses
+// BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php
+//
+// Please contact the author if you need another license.
+// This module is provided "as is", without warranties of any kind.
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * An LRU cache, based on <code>LinkedHashMap</code>.
+ * 
+ * <p>
+ * This cache has a fixed maximum number of elements (<code>cacheSize</code>).
+ * If the cache is full and another entry is added, the LRU (least recently
+ * used) entry is dropped.
+ * 
+ * <p>
+ * This class is thread-safe. All methods of this class are synchronized.
+ * 
+ * <p>
+ * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br>
+ * Multi-licensed: EPL / LGPL / GPL / AL / BSD.
+ */
+public class LRUCache<K, V> {
+
+	private static final float hashTableLoadFactor = 0.75f;
+
+	private LinkedHashMap<K, V> map;
+	private int cacheSize;
+
+	/**
+	 * Creates a new LRU cache.
+	 * 
+	 * @param cacheSize
+	 *            the maximum number of entries that will be kept in this cache.
+	 */
+	public LRUCache(int cacheSize) {
+		this.cacheSize = cacheSize;
+		int hashTableCapacity = (int) Math.ceil(cacheSize / LRUCache.hashTableLoadFactor) + 1;
+		this.map = new LinkedHashMap<K, V>(hashTableCapacity, LRUCache.hashTableLoadFactor, true) {
+			// (an anonymous inner class)
+			private static final long serialVersionUID = 1;
+
+			@Override
+			protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+				return this.size() > LRUCache.this.cacheSize;
+			}
+		};
+	}
+
+	/**
+	 * Retrieves an entry from the cache.<br>
+	 * The retrieved entry becomes the MRU (most recently used) entry.
+	 * 
+	 * @param key
+	 *            the key whose associated value is to be returned.
+	 * @return the value associated to this key, or null if no value with this
+	 *         key exists in the cache.
+	 */
+	public synchronized V get(K key) {
+		return this.map.get(key);
+	}
+
+	/**
+	 * Adds an entry to this cache.
+	 * The new entry becomes the MRU (most recently used) entry.
+	 * If an entry with the specified key already exists in the cache, it is
+	 * replaced by the new entry.
+	 * If the cache is full, the LRU (least recently used) entry is removed from
+	 * the cache.
+	 * 
+	 * @param key
+	 *            the key with which the specified value is to be associated.
+	 * @param value
+	 *            a value to be associated with the specified key.
+	 */
+	public synchronized void put(K key, V value) {
+		this.map.put(key, value);
+	}
+
+	/**
+	 * Clears the cache.
+	 */
+	public synchronized void clear() {
+		this.map.clear();
+	}
+
+	/**
+	 * Returns the number of used entries in the cache.
+	 * 
+	 * @return the number of entries currently in the cache.
+	 */
+	public synchronized int usedEntries() {
+		return this.map.size();
+	}
+
+	/**
+	 * Returns a <code>Collection</code> that contains a copy of all cache
+	 * entries.
+	 * 
+	 * @return a <code>Collection</code> with a copy of the cache content.
+	 */
+	public synchronized Collection<Map.Entry<K, V>> getAll() {
+		return new ArrayList<Map.Entry<K, V>>(this.map.entrySet());
+	}
+
+} // end class LRUCache

+ 107 - 61
engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGrid.java

@@ -11,6 +11,8 @@ import com.jme3.terrain.heightmap.HeightMap;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import com.jme3.material.Material;
@@ -35,6 +37,37 @@ public class TerrainGrid extends TerrainQuad {
     private Vector3f[] quadIndex;
     private Map<String, TerrainGridListener> listeners = new HashMap<String, TerrainGridListener>();
     private Material material;
+    private LRUCache<Vector3f, TerrainQuad> cache = new LRUCache<Vector3f, TerrainQuad>(16);
+
+    private class UpdateQuadCache implements Runnable {
+
+        private final Vector3f location;
+        private final boolean centerOnly;
+
+        public UpdateQuadCache(Vector3f location) {
+            this.location = location;
+            this.centerOnly = false;
+        }
+
+        public UpdateQuadCache(Vector3f location, boolean centerOnly) {
+            this.location = location;
+            this.centerOnly = centerOnly;
+        }
+
+        public void run() {
+            for (int i = centerOnly ? 1 : 0; i < (centerOnly ? 3 : 4); i++) {
+                for (int j = centerOnly ? 1 : 0; j < (centerOnly ? 3 : 4); j++) {
+                    Vector3f temp = location.add(quadIndex[i * 4 + j]);
+                    if (cache.get(temp) == null) {
+                        HeightMap heightMapAt = heightMapGrid.getHeightMapAt(temp);
+                        TerrainQuad q = new TerrainQuad(getName() + "Quad" + temp, patchSize, quadSize, heightMapAt == null ? null : heightMapAt.getHeightMap(), lodCalculatorFactory);
+                        cache.put(temp, q);
+                    }
+                }
+            }
+
+        }
+    }
 
     public TerrainGrid(String name, int patchSize, int size, Vector3f stepScale, HeightMapGrid heightMapGrid, int totalSize,
             Vector2f offset, float offsetAmount, LodCalculatorFactory lodCalculatorFactory) {
@@ -57,7 +90,11 @@ public class TerrainGrid extends TerrainQuad {
             new Vector3f(-this.quarterSize, 0, this.quarterSize).mult(this.stepScale),
             new Vector3f(this.quarterSize, 0, -this.quarterSize).mult(this.stepScale),
             new Vector3f(this.quarterSize, 0, this.quarterSize).mult(this.stepScale)};
-        this.quadIndex = new Vector3f[]{new Vector3f(0, 0, 0), new Vector3f(0, 0, 1), new Vector3f(1, 0, 0), new Vector3f(1, 0, 1)};
+        this.quadIndex = new Vector3f[]{
+            new Vector3f(-1, 0, -1), new Vector3f(-1, 0, 0), new Vector3f(-1, 0, 1), new Vector3f(-1, 0, 2),
+            new Vector3f(0, 0, -1), new Vector3f(0, 0, 0), new Vector3f(0, 0, 1), new Vector3f(0, 0, 2),
+            new Vector3f(1, 0, -1), new Vector3f(1, 0, 0), new Vector3f(1, 0, 1), new Vector3f(1, 0, 2),
+            new Vector3f(2, 0, -1), new Vector3f(2, 0, 0), new Vector3f(2, 0, 1), new Vector3f(2, 0, 2)};
 
         updateChildrens(Vector3f.ZERO);
     }
@@ -93,7 +130,7 @@ public class TerrainGrid extends TerrainQuad {
             }
         }
 
-        //super.update(locations);
+        super.update(locations);
     }
 
     public Vector3f getCell(Vector3f location) {
@@ -102,7 +139,9 @@ public class TerrainGrid extends TerrainQuad {
     }
 
     protected void removeQuad(int idx) {
-        this.detachChild(this.getQuad(idx));
+        if (this.getQuad(idx) != null) {
+            this.detachChild(this.getQuad(idx));
+        }
     }
 
     protected void moveQuad(int from, int to) {
@@ -112,16 +151,66 @@ public class TerrainGrid extends TerrainQuad {
         fq.setLocalTranslation(this.quadOrigins[to - 1]);
     }
 
-    protected TerrainQuad createQuadAt(Vector3f location, int quadrant) {
-        final HeightMap heightMapAt = this.heightMapGrid.getHeightMapAt(location);
-        TerrainQuad q = new TerrainQuad(this.getName() + "Quad" + location, this.patchSize, this.quadSize, heightMapAt == null ? null : heightMapAt.getHeightMap(), this.lodCalculatorFactory);
-        q.setLocalTranslation(this.quadOrigins[quadrant - 1]);
+    protected void attachQuadAt(TerrainQuad q, int quadrant) {
         q.setMaterial(this.material);
+        q.setLocalTranslation(quadOrigins[quadrant - 1]);
         q.setQuadrant((short) quadrant);
-        return q;
+        this.attachChild(q);
     }
 
     private void updateChildrens(Vector3f cam) {
+        TerrainQuad q1 = cache.get(cam.add(quadIndex[5]));
+        TerrainQuad q2 = cache.get(cam.add(quadIndex[6]));
+        TerrainQuad q3 = cache.get(cam.add(quadIndex[9]));
+        TerrainQuad q4 = cache.get(cam.add(quadIndex[10]));
+
+        int dx = 0;
+        int dy = 0;
+        if (currentCell != null) {
+            dx = (int) (cam.x - currentCell.x);
+            dy = (int) (cam.z - currentCell.z);
+        }
+
+        int kxm = 0;
+        int kxM = 4;
+        int kym = 0;
+        int kyM = 4;
+        if (dx == -1) {
+            kxM = 3;
+        } else if (dx == 1) {
+            kxm = 1;
+        }
+
+        if (dy == -1) {
+            kyM = 3;
+        } else if (dy == 1) {
+            kym = 1;
+        }
+
+        for (int i=kym; i<kyM; i++) {
+            for (int j = kxm; j < kxM; j++) {
+                cache.get(cam.add(quadIndex[i * 4 + j]));
+            }
+        }
+
+        if (q1 == null || q2 == null || q3 == null || q4 == null) {
+            try {
+                executor.submit(new UpdateQuadCache(cam, true)).get();
+                q1 = cache.get(cam.add(quadIndex[5]));
+                q2 = cache.get(cam.add(quadIndex[6]));
+                q3 = cache.get(cam.add(quadIndex[9]));
+                q4 = cache.get(cam.add(quadIndex[10]));
+            } catch (InterruptedException ex) {
+                Logger.getLogger(TerrainGrid.class.getName()).log(Level.SEVERE, null, ex);
+                return;
+            } catch (ExecutionException ex) {
+                Logger.getLogger(TerrainGrid.class.getName()).log(Level.SEVERE, null, ex);
+                return;
+            }
+        }
+
+        executor.execute(new UpdateQuadCache(cam));
+
         RigidBodyControl control = getControl(RigidBodyControl.class);
         PhysicsSpace space = null;
         if (control != null) {
@@ -129,59 +218,16 @@ public class TerrainGrid extends TerrainQuad {
             space.remove(this);
             this.removeControl(control);
         }
-        int dx = (int) cam.x;
-        int dz = (int) cam.z;
-        if (this.currentCell != null) {
-            dx -= (int) (this.currentCell.x);
-            dz -= (int) (this.currentCell.z);
-        }
-        if (this.currentCell == null || FastMath.abs(dx) > 1 || FastMath.abs(dz) > 1 || (dx != 0 && dz != 0)) {
-            if (this.currentCell != null) {
-                // in case of teleport, otherwise the FastMath.abs(delta) should
-                // never be greater than 1
-                this.removeQuad(1);
-                this.removeQuad(2);
-                this.removeQuad(3);
-                this.removeQuad(4);
-            }
-            this.attachChild(this.createQuadAt(cam.add(this.quadIndex[0]).mult(this.quadSize - 1), 1));
-            this.attachChild(this.createQuadAt(cam.add(this.quadIndex[1]).mult(this.quadSize - 1), 2));
-            this.attachChild(this.createQuadAt(cam.add(this.quadIndex[2]).mult(this.quadSize - 1), 3));
-            this.attachChild(this.createQuadAt(cam.add(this.quadIndex[3]).mult(this.quadSize - 1), 4));
-        } else if (dx == 0) {
-            if (dz < 0) {
-                // move north
-                this.moveQuad(1, 2);
-                this.moveQuad(3, 4);
-                this.attachChild(this.createQuadAt(cam.add(this.quadIndex[0]).mult(this.quadSize - 1), 1));
-                this.attachChild(this.createQuadAt(cam.add(this.quadIndex[2]).mult(this.quadSize - 1), 3));
-            } else {
-                // move south
-                this.moveQuad(2, 1);
-                this.moveQuad(4, 3);
-                this.attachChild(this.createQuadAt(cam.add(this.quadIndex[1]).mult(this.quadSize - 1), 2));
-                this.attachChild(this.createQuadAt(cam.add(this.quadIndex[3]).mult(this.quadSize - 1), 4));
-            }
-        } else if (dz == 0) {
-            if (dx < 0) {
-                // move west
-                this.moveQuad(1, 3);
-                this.moveQuad(2, 4);
-                this.attachChild(this.createQuadAt(cam.add(this.quadIndex[0]).mult(this.quadSize - 1), 1));
-                this.attachChild(this.createQuadAt(cam.add(this.quadIndex[1]).mult(this.quadSize - 1), 2));
-            } else {
-                // move east
-                this.moveQuad(3, 1);
-                this.moveQuad(4, 2);
-                this.attachChild(this.createQuadAt(cam.add(this.quadIndex[2]).mult(this.quadSize - 1), 3));
-                this.attachChild(this.createQuadAt(cam.add(this.quadIndex[3]).mult(this.quadSize - 1), 4));
-            }
-        } else {
-            // rare situation to enter into a diagonally placed cell
-            // could not get into this part while testing, as it is handled by moving first
-            // in either horizontally or vertically than the other way
-            // I handle it in the first IF
-        }
+
+        this.removeQuad(1);
+        this.removeQuad(2);
+        this.removeQuad(3);
+        this.removeQuad(4);
+        attachQuadAt(q1, 1);
+        attachQuadAt(q2, 2);
+        attachQuadAt(q3, 3);
+        attachQuadAt(q4, 4);
+
         this.currentCell = cam;
         this.setLocalTranslation(cam.mult(2 * this.quadSize));
 

+ 28 - 26
engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java

@@ -29,7 +29,6 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
 package com.jme3.terrain.geomipmap;
 
 import com.jme3.export.InputCapsule;
@@ -57,7 +56,7 @@ import java.util.ArrayList;
  * NOTE: right now it just uses the first camera passed in,
  * in the future it will use all of them to determine what
  * LOD to set.
- * 
+ *
  * @author Brent Owens
  */
 public class TerrainLodControl extends AbstractControl {
@@ -68,8 +67,8 @@ public class TerrainLodControl extends AbstractControl {
 
     public TerrainLodControl() {
     }
-    
-   /**
+
+    /**
      * Only uses the first camera right now.
      * @param terrain to act upon (must be a Spatial)
      * @param cameras one or more cameras to reference for LOD calc
@@ -80,52 +79,55 @@ public class TerrainLodControl extends AbstractControl {
         }
         this.cameras = cameras;
     }
-	
-	@Override
-	protected void controlRender(RenderManager rm, ViewPort vp) {
-		
-	}
 
-	@Override
-	protected void controlUpdate(float tpf) {
-		//list of cameras for when terrain supports multiple cameras (ie split screen)
-        
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+    }
+
+    @Override
+    protected void controlUpdate(float tpf) {
+        //list of cameras for when terrain supports multiple cameras (ie split screen)
+
         if (cameras != null) {
             if (cameraLocations.isEmpty() && !cameras.isEmpty()) {
                 for (Camera c : cameras) // populate them
+                {
                     cameraLocations.add(c.getLocation());
+                }
             }
             terrain.update(cameraLocations);
         }
-	}
+    }
 
-	public Control cloneForSpatial(Spatial spatial) {
-		if (spatial instanceof Terrain) {
+    public Control cloneForSpatial(Spatial spatial) {
+        if (spatial instanceof Terrain) {
             List<Camera> cameraClone = new ArrayList<Camera>();
             if (cameras != null) {
-            for (Camera c : cameras)
-                cameraClone.add(c);
+                for (Camera c : cameras) {
+                    cameraClone.add(c);
+                }
             }
-			return new TerrainLodControl((TerrainQuad)spatial, cameraClone);
+            return new TerrainLodControl((TerrainQuad) spatial, cameraClone);
         }
-		return null;
-	}
-
+        return null;
+    }
 
     public void setCameras(List<Camera> cameras) {
         this.cameras = cameras;
         cameraLocations.clear();
-        for (Camera c : cameras)
+        for (Camera c : cameras) {
             cameraLocations.add(c.getLocation());
+        }
     }
 
     @Override
     public void setSpatial(Spatial spatial) {
         super.setSpatial(spatial);
-        if (spatial instanceof TerrainQuad)
-            this.terrain = (TerrainQuad)spatial;
+        if (spatial instanceof TerrainQuad) {
+            this.terrain = (TerrainQuad) spatial;
+        }
     }
-    
+
     public void setTerrain(TerrainQuad terrain) {
         this.terrain = terrain;
     }

+ 69 - 69
engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java

@@ -73,9 +73,9 @@ import java.util.logging.Logger;
  * A terrain quad is a node in the quad tree of the terrain system.
  * The root terrain quad will be the only one that receives the update() call every frame
  * and it will determine if there has been any LOD change.
- * 
+ *
  * The leaves of the terrain quad tree are Terrain Patches. These have the real geometry mesh.
- * 
+ *
  * @author Brent Owens
  */
 public class TerrainQuad extends Node implements Terrain {
@@ -93,7 +93,7 @@ public class TerrainQuad extends Node implements Terrain {
 	protected float offsetAmount;
 
 	protected int quadrant = 1; // 1=upper left, 2=lower left, 3=upper right, 4=lower right
-	
+
 	protected LodCalculatorFactory lodCalculatorFactory;
 
 
@@ -107,20 +107,20 @@ public class TerrainQuad extends Node implements Terrain {
 
     private TerrainPicker picker;
 
-	
-	private ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
+
+	protected ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
 		           public Thread newThread(Runnable r) {
 		             Thread th = new Thread(r);
 		             th.setDaemon(true);
 		             return th;
 		           }
 		    });
-	
-	
+
+
 	public TerrainQuad() {
 		super("Terrain");
 	}
-	
+
 	public TerrainQuad(String name, int patchSize, int totalSize, float[] heightMap) {
 		this(name, patchSize, totalSize, heightMap, null);
 	}
@@ -135,7 +135,7 @@ public class TerrainQuad extends Node implements Terrain {
         fixNormalEdges(affectedAreaBBox);
         addControl(new NormalRecalcControl(this));
 	}
-	
+
 	protected TerrainQuad(String name, int patchSize, int size,
 				Vector3f stepScale, float[] heightMap, int totalSize,
 				Vector2f offset, float offsetAmount,
@@ -145,7 +145,7 @@ public class TerrainQuad extends Node implements Terrain {
 		if (!FastMath.isPowerOfTwo(size - 1)) {
 			throw new RuntimeException("size given: " + size + "  Terrain quad sizes may only be (2^N + 1)");
 		}
-		
+
 		if (heightMap == null)
 			heightMap = generateDefaultHeightMap(size);
 
@@ -158,7 +158,7 @@ public class TerrainQuad extends Node implements Terrain {
 		this.lodCalculatorFactory = lodCalculatorFactory;
 		split(patchSize, heightMap);
 	}
-	 
+
 	public void setLodCalculatorFactory(LodCalculatorFactory lodCalculatorFactory) {
 		if (children != null) {
 			for (int i = children.size(); --i >= 0;) {
@@ -171,8 +171,8 @@ public class TerrainQuad extends Node implements Terrain {
 			}
 		}
 	}
-	
-	
+
+
 	/**
 	 * Create just a flat heightmap
 	 */
@@ -184,11 +184,11 @@ public class TerrainQuad extends Node implements Terrain {
 	 /**
 	  * Call from the update() method of a terrain controller to update
 	  * the LOD values of each patch.
-	  * This will perform the geometry calculation in a background thread and 
+	  * This will perform the geometry calculation in a background thread and
 	  * do the actual update on the opengl thread.
 	  */
 	public void update(List<Vector3f> locations) {
-		
+
         updateLOD(locations);
 	}
 
@@ -197,12 +197,12 @@ public class TerrainQuad extends Node implements Terrain {
      * Should only be called on the root quad
      */
     protected void updateNormals() {
-        
+
         if (needToRecalculateNormals()) {
             //TODO background-thread this if it ends up being expensive
             fixNormals(affectedAreaBBox); // the affected patches
             fixNormalEdges(affectedAreaBBox); // the edges between the patches
-            
+
             setNormalRecalcNeeded(null); // set to false
         }
     }
@@ -234,11 +234,11 @@ public class TerrainQuad extends Node implements Terrain {
 		UpdateLOD updateLodThread = new UpdateLOD(locations);
 		executor.execute(updateLodThread);
     }
-	
+
 	private synchronized boolean isLodCalcRunning() {
 		return lodCalcRunning;
 	}
-	
+
 	private synchronized void setLodCalcRunning(boolean running) {
 		lodCalcRunning = running;
 	}
@@ -327,17 +327,17 @@ public class TerrainQuad extends Node implements Terrain {
     public float getTextureCoordinateScale() {
         return 1f/(float)totalSize;
     }
-	
+
 	/**
 	 * Calculates the LOD of all child terrain patches.
 	 */
 	private class UpdateLOD implements Runnable {
 		private List<Vector3f> camLocations;
-		
+
 		UpdateLOD(List<Vector3f> location) {
 			camLocations = location;
 		}
-		
+
 		public void run() {
 			long start = System.currentTimeMillis();
 			if (isLodCalcRunning()) {
@@ -346,11 +346,11 @@ public class TerrainQuad extends Node implements Terrain {
 			}
 			//System.out.println("spawned thread "+toString());
 			setLodCalcRunning(true);
-			
+
 			// go through each patch and calculate its LOD based on camera distance
 			HashMap<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>();
 			boolean lodChanged = calculateLod(camLocations, updated); // 'updated' gets populated here
-			
+
 			if (!lodChanged) {
 				// not worth updating anything else since no one's LOD changed
 				setLodCalcRunning(false);
@@ -358,28 +358,28 @@ public class TerrainQuad extends Node implements Terrain {
 			}
 			// then calculate its neighbour LOD values for seaming in the shader
 			findNeighboursLod(updated);
-			
+
 			fixEdges(updated); // 'updated' can get added to here
-			
+
 			reIndexPages(updated);
-			
+
 			setUpdateQuadLODs(updated); // set back to main ogl thread
-			
+
 			setLodCalcRunning(false);
 			//double duration = (System.currentTimeMillis()-start);
 			//System.out.println("terminated in "+duration);
 		}
 
-		
-		
+
+
 	}
-	
+
 	private void setUpdateQuadLODs(HashMap<String,UpdatedTerrainPatch> updated) {
 		synchronized (updatePatchesLock) {
 			updatedPatches = updated;
 		}
 	}
-	
+
 	/**
 	 * Back on the ogl thread: update the terrain patch geometries
 	 * @param updatedPatches to be updated
@@ -390,20 +390,20 @@ public class TerrainQuad extends Node implements Terrain {
 			//	return;
 			if (updatedPatches == null || updatedPatches.isEmpty())
 				return;
-			
+
 			//TODO do the actual geometry update here
 			for (UpdatedTerrainPatch utp : updatedPatches.values()) {
 				utp.updateAll();
 			}
-			
+
 			updatedPatches.clear();
 		}
 	}
-	
+
 	protected boolean calculateLod(List<Vector3f> location, HashMap<String,UpdatedTerrainPatch> updates) {
-		
+
 		boolean lodChanged = false;
-		
+
 		if (children != null) {
 			for (int i = children.size(); --i >= 0;) {
 				Spatial child = children.get(i);
@@ -418,10 +418,10 @@ public class TerrainQuad extends Node implements Terrain {
 				}
 			}
 		}
-		
+
 		return lodChanged;
 	}
-	
+
 	protected synchronized void findNeighboursLod(HashMap<String,UpdatedTerrainPatch> updated) {
 		if (children != null) {
 			for (int x = children.size(); --x >= 0;) {
@@ -441,20 +441,20 @@ public class TerrainQuad extends Node implements Terrain {
 					}
 					TerrainPatch right = patch.rightNeighbour;
 					TerrainPatch down = patch.bottomNeighbour;
-					
+
 					UpdatedTerrainPatch utp = updated.get(patch.getName());
 					if (utp == null) {
 						utp = new UpdatedTerrainPatch(patch, patch.lod);
 						updated.put(utp.getName(), utp);
 					}
-					
+
 					if (right != null) {
 						UpdatedTerrainPatch utpR = updated.get(right.getName());
 						if (utpR == null) {
 							utpR = new UpdatedTerrainPatch(right, right.lod);
 							updated.put(utpR.getName(), utpR);
 						}
-						
+
 						utp.setRightLod(utpR.getNewLod());
 						utpR.setLeftLod(utp.getNewLod());
 					}
@@ -464,16 +464,16 @@ public class TerrainQuad extends Node implements Terrain {
 							utpD = new UpdatedTerrainPatch(down, down.lod);
 							updated.put(utpD.getName(), utpD);
 						}
-						
+
 						utp.setBottomLod(utpD.getNewLod());
 						utpD.setTopLod(utp.getNewLod());
 					}
-					
+
 				}
 			}
 		}
 	}
-	
+
 	/**
 	 * Find any neighbours that should have their edges seamed because another neighbour
 	 * changed its LOD to a greater value (less detailed)
@@ -487,7 +487,7 @@ public class TerrainQuad extends Node implements Terrain {
 				} else if (child instanceof TerrainPatch) {
 					TerrainPatch patch = (TerrainPatch) child;
 					UpdatedTerrainPatch utp = updated.get(patch.getName());
-					
+
 					if(utp.lodChanged()) {
 						if (!patch.searchedForNeighboursAlready) {
 							// set the references to the neighbours
@@ -538,7 +538,7 @@ public class TerrainQuad extends Node implements Terrain {
 			}
 		}
 	}
-	
+
 	protected synchronized void reIndexPages(HashMap<String,UpdatedTerrainPatch> updated) {
 		if (children != null) {
 			for (int i = children.size(); --i >= 0;) {
@@ -557,7 +557,7 @@ public class TerrainQuad extends Node implements Terrain {
 	 * children are either pages or blocks. This is dependent on the size of the
 	 * children. If the child's size is less than or equal to the set block
 	 * size, then blocks are created, otherwise, pages are created.
-	 * 
+	 *
 	 * @param blockSize
 	 *			the blocks size to test against.
 	 * @param heightMap
@@ -784,7 +784,7 @@ public class TerrainQuad extends Node implements Terrain {
 		patch4.setLodCalculator(lodCalculatorFactory);
         TangentBinormalGenerator.generate(patch4);
 	}
-	
+
 	public float[] createHeightSubBlock(float[] heightMap, int x,
 			int y, int side) {
 		float[] rVal = new float[side * side];
@@ -804,7 +804,7 @@ public class TerrainQuad extends Node implements Terrain {
      * A handy method that will attach all bounding boxes of this terrain
      * to the node you supply.
      * Useful to visualize the bounding boxes when debugging.
-     * 
+     *
      * @param parent that will get the bounding box shapes of the terrain attached to
      */
     public void attachBoundChildren(Node parent) {
@@ -1025,7 +1025,7 @@ public class TerrainQuad extends Node implements Terrain {
 
         if (!isPointOnTerrain(x,z))
             return;
-        
+
         adjustHeight(x, z,delta);
 
         setNormalRecalcNeeded(xz);
@@ -1075,7 +1075,7 @@ public class TerrainQuad extends Node implements Terrain {
         }
     }
 
-    
+
     // a position can be in multiple quadrants, so use a bit anded value.
     private int findQuadrant(int x, int y) {
         int split = (size + 1) >> 1;
@@ -1109,7 +1109,7 @@ public class TerrainQuad extends Node implements Terrain {
         }
     }
 
-	
+
 	public int getQuadrant() {
 		return quadrant;
 	}
@@ -1117,7 +1117,7 @@ public class TerrainQuad extends Node implements Terrain {
 	public void setQuadrant(short quadrant) {
 		this.quadrant = quadrant;
 	}
-	
+
 
 	protected TerrainPatch getPatch(int quad) {
 		if (children != null)
@@ -1183,8 +1183,8 @@ public class TerrainQuad extends Node implements Terrain {
 
 		return null;
 	}
-	
-	
+
+
 	protected TerrainPatch findTopPatch(TerrainPatch tp) {
 		if (tp.getQuadrant() == 2)
 			return getPatch(1);
@@ -1203,7 +1203,7 @@ public class TerrainQuad extends Node implements Terrain {
 
 		return null;
 	}
-	
+
 	protected TerrainPatch findLeftPatch(TerrainPatch tp) {
 		if (tp.getQuadrant() == 3)
 			return getPatch(1);
@@ -1268,7 +1268,7 @@ public class TerrainQuad extends Node implements Terrain {
 
 		return null;
 	}
-	
+
 	protected TerrainQuad findTopQuad() {
 		if (getParent() == null || !(getParent() instanceof TerrainQuad))
 			return null;
@@ -1291,7 +1291,7 @@ public class TerrainQuad extends Node implements Terrain {
 
 		return null;
 	}
-	
+
 	protected TerrainQuad findLeftQuad() {
 		if (getParent() == null || !(getParent() instanceof TerrainQuad))
 			return null;
@@ -1336,7 +1336,7 @@ public class TerrainQuad extends Node implements Terrain {
             }
         }
     }
-	
+
 	/**
 	 * fix the normals on the edge of the terrain patches.
 	 */
@@ -1372,13 +1372,13 @@ public class TerrainQuad extends Node implements Terrain {
                     bottomLeft = findDownPatch(left);
 
                 tp.fixNormalEdges(right, bottom, top, left, bottomRight, bottomLeft, topRight, topLeft);
-                
+
             }
         } // for each child
 
 	}
 
-    
+
 
     @Override
     public int collideWith(Collidable other, CollisionResults results){
@@ -1397,7 +1397,7 @@ public class TerrainQuad extends Node implements Terrain {
         }
         return total;
     }
-	
+
     /**
      * Gather the terrain patches that intersect the given ray (toTest).
      * This only tests the bounding boxes
@@ -1405,7 +1405,7 @@ public class TerrainQuad extends Node implements Terrain {
      * @param results
      */
     public void findPick(Ray toTest, List<TerrainPickData> results) {
-        
+
         if (getWorldBound() != null) {
             if (getWorldBound().intersects(toTest)) {
                 // further checking needed.
@@ -1460,7 +1460,7 @@ public class TerrainQuad extends Node implements Terrain {
 			}
 		}
 	}
-	
+
 	@Override
 	public void read(JmeImporter e) throws IOException {
 		super.read(e);
@@ -1472,7 +1472,7 @@ public class TerrainQuad extends Node implements Terrain {
 		quadrant = c.readInt("quadrant", 0);
 		totalSize = c.readInt("totalSize", 0);
 		lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null);
-        
+
         // the terrain is re-built on load, so we need to run this once
         //affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size, Float.MAX_VALUE, size);
         //updateNormals();
@@ -1541,13 +1541,13 @@ public class TerrainQuad extends Node implements Terrain {
     public int getTotalSize() {
         return totalSize;
     }
-    
+
 
 	public float[] getHeightMap() {
 
         //if (true)
         //    return heightMap;
-        
+
         float[] hm = null;
         int length = ((size-1)/2)+1;
         int area = size*size;
@@ -1576,7 +1576,7 @@ public class TerrainQuad extends Node implements Terrain {
             }
 
             // combine them into a single heightmap
-            
+
 
             // first upper blocks
             for (int y=0; y<length; y++) { // rows

+ 75 - 0
engine/src/terrain/com/jme3/terrain/heightmap/FractalHeightMapGrid.java

@@ -0,0 +1,75 @@
+package com.jme3.terrain.heightmap;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import javax.imageio.ImageIO;
+
+import org.novyon.noise.Basis;
+
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.MapUtils;
+
+public class FractalHeightMapGrid implements HeightMapGrid {
+
+	public class FloatBufferHeightMap extends AbstractHeightMap {
+
+		private final FloatBuffer buffer;
+
+		public FloatBufferHeightMap(FloatBuffer buffer) {
+			this.buffer = buffer;
+		}
+
+		@Override
+		public boolean load() {
+			this.heightData = this.buffer.array();
+			return true;
+		}
+
+	}
+
+	private int size;
+	private final Basis base;
+	private final String cacheDir;
+	private final float heightScale;
+
+	public FractalHeightMapGrid(Basis base, String cacheDir, float heightScale) {
+		this.base = base;
+		this.cacheDir = cacheDir;
+		this.heightScale = heightScale;
+	}
+
+	@Override
+	public HeightMap getHeightMapAt(Vector3f location) {
+		AbstractHeightMap heightmap = null;
+		if (this.cacheDir != null && new File(this.cacheDir, "terrain_" + (int) location.x + "_" + (int) location.z + ".png").exists()) {
+			try {
+				BufferedImage im = null;
+				im = ImageIO.read(new File(this.cacheDir, "terrain_" + (int) location.x + "_" + (int) location.z + ".png"));
+				heightmap = new Grayscale16BitHeightMap(im);
+				heightmap.setHeightScale(256);
+			} catch (IOException e) {}
+		} else {
+			FloatBuffer buffer = this.base.getBuffer(location.x * (this.size - 1), location.z * (this.size - 1), 0, this.size);
+			if (this.cacheDir != null) {
+				MapUtils.saveImage(MapUtils.toGrayscale16Image(buffer, this.size), new File(this.cacheDir, "terrain_" + (int) location.x
+						+ "_" + (int) location.z + ".png"));
+			}
+			float[] arr = buffer.array();
+			for (int i = 0; i < arr.length; i++) {
+				arr[i] = arr[i] * this.heightScale;
+			}
+			heightmap = new FloatBufferHeightMap(buffer);
+		}
+		heightmap.load();
+		return heightmap;
+	}
+
+	@Override
+	public void setSize(int size) {
+		this.size = size;
+	}
+
+}

+ 6 - 2
engine/src/terrain/com/jme3/terrain/heightmap/ImageBasedHeightMapGrid.java

@@ -12,6 +12,8 @@ import com.jme3.texture.Texture;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.imageio.ImageIO;
 import jme3tools.converters.ImageToAwt;
 
@@ -34,16 +36,18 @@ public class ImageBasedHeightMapGrid implements HeightMapGrid {
 
     public HeightMap getHeightMapAt(Vector3f location) {
         // HEIGHTMAP image (for the terrain heightmap)
-        int x = (int) (FastMath.floor(location.x / this.size) * this.size);
-        int z = (int) (FastMath.floor(location.z / this.size) * this.size);
+        int x = (int) location.x;
+        int z = (int) location.z;
         AbstractHeightMap heightmap = null;
         try {
+            Logger.getLogger(ImageBasedHeightMapGrid.class.getCanonicalName()).log(Level.INFO, "Loading heightmap from file: " + textureBase + "_" + x + "_" + z + "." + textureExt);
             final InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(textureBase + "_" + x + "_" + z + "." + textureExt);
             BufferedImage im = null;
             if (stream != null) {
                 im = ImageIO.read(stream);
             } else {
                 im = new BufferedImage(size, size, BufferedImage.TYPE_USHORT_GRAY);
+                Logger.getLogger(ImageBasedHeightMapGrid.class.getCanonicalName()).log(Level.INFO, "File: " + textureBase + "_" + x + "_" + z + "." + textureExt + " not found, loading zero heightmap instead");
             }
             // CREATE HEIGHTMAP
             heightmap = new Grayscale16BitHeightMap(im);

+ 52 - 3
engine/src/test/jme3test/terrain/TerrainGridTest.java

@@ -20,9 +20,18 @@ import com.jme3.renderer.Camera;
 import com.jme3.terrain.geomipmap.TerrainGrid;
 import com.jme3.terrain.geomipmap.TerrainLodControl;
 import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.heightmap.FractalHeightMapGrid;
 import com.jme3.terrain.heightmap.ImageBasedHeightMapGrid;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture.WrapMode;
+import org.novyon.noise.ShaderUtils;
+import org.novyon.noise.basis.FilteredBasis;
+import org.novyon.noise.filter.IterativeFilter;
+import org.novyon.noise.filter.OptimizedErode;
+import org.novyon.noise.filter.PerturbFilter;
+import org.novyon.noise.filter.SmoothFilter;
+import org.novyon.noise.fractal.FractalSum;
+import org.novyon.noise.modulator.NoiseModulator;
 
 public class TerrainGridTest extends SimpleApplication {
 
@@ -31,13 +40,18 @@ public class TerrainGridTest extends SimpleApplication {
     private float grassScale = 64;
     private float dirtScale = 16;
     private float rockScale = 128;
-    private boolean usePhysics = false;
+    private boolean usePhysics = true;
 
     public static void main(final String[] args) {
         TerrainGridTest app = new TerrainGridTest();
         app.start();
     }
     private CharacterControl player3;
+    private FractalSum base;
+    private PerturbFilter perturb;
+    private OptimizedErode therm;
+    private SmoothFilter smooth;
+    private IterativeFilter iterate;
 
     @Override
     public void simpleInitApp() {
@@ -70,8 +84,43 @@ public class TerrainGridTest extends SimpleApplication {
         mat_terrain.setTexture("Tex3", rock);
         mat_terrain.setFloat("Tex3Scale", rockScale);
 
-        this.terrain = new TerrainGrid("terrain", 65, 1025, new ImageBasedHeightMapGrid("Textures/Terrain/grid/mountains", "png",
-                this.assetManager));
+        this.base = new FractalSum();
+        this.base.setRoughness(0.7f);
+        this.base.setFrequency(1.0f);
+        this.base.setAmplitude(1.0f);
+        this.base.setLacunarity(2.12f);
+        this.base.setOctaves(8);
+        this.base.setScale(0.02125f);
+        this.base.addModulator(new NoiseModulator() {
+
+            @Override
+            public float value(float... in) {
+                return ShaderUtils.clamp(in[0] * 0.5f + 0.5f, 0, 1);
+            }
+        });
+
+        FilteredBasis ground = new FilteredBasis(this.base);
+
+        this.perturb = new PerturbFilter();
+        this.perturb.setMagnitude(0.119f);
+
+        this.therm = new OptimizedErode();
+        this.therm.setRadius(5);
+        this.therm.setTalus(0.011f);
+
+        this.smooth = new SmoothFilter();
+        this.smooth.setRadius(1);
+        this.smooth.setEffect(0.7f);
+
+        this.iterate = new IterativeFilter();
+        this.iterate.addPreFilter(this.perturb);
+        this.iterate.addPostFilter(this.smooth);
+        this.iterate.setFilter(this.therm);
+        this.iterate.setIterations(1);
+
+        ground.addPreFilter(this.iterate);
+
+        this.terrain = new TerrainGrid("terrain", 65, 257, new FractalHeightMapGrid(ground, null, 256f));
 
         this.terrain.setMaterial(this.mat_terrain);
         this.terrain.setLocalTranslation(0, 0, 0);