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
3.0
ant..om 14 years ago
parent 1fbd05a3ca
commit 58ede837c6
  1. 2
      engine/lib/nblibraries.properties
  2. BIN
      engine/lib/noise/noise-0.0.1-SNAPSHOT.jar
  3. 3
      engine/nbproject/project.properties
  4. 62
      engine/src/terrain/com/jme3/terrain/MapUtils.java
  5. 124
      engine/src/terrain/com/jme3/terrain/geomipmap/LRUCache.java
  6. 158
      engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGrid.java
  7. 14
      engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java
  8. 2
      engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java
  9. 75
      engine/src/terrain/com/jme3/terrain/heightmap/FractalHeightMapGrid.java
  10. 8
      engine/src/terrain/com/jme3/terrain/heightmap/ImageBasedHeightMapGrid.java
  11. 55
      engine/src/test/jme3test/terrain/TerrainGridTest.java

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

@ -39,7 +39,8 @@ javac.classpath=\
${libs.lwjgl.classpath}:\ ${libs.lwjgl.classpath}:\
${libs.jheora.classpath}:\ ${libs.jheora.classpath}:\
${libs.niftygui1.3.classpath}:\ ${libs.niftygui1.3.classpath}:\
${libs.jme3-test-data.classpath} ${libs.jme3-test-data.classpath}:\
${libs.noise.classpath}
# Space-separated list of extra javac options # Space-separated list of extra javac options
javac.compilerargs= javac.compilerargs=
javac.deprecation=false javac.deprecation=false

@ -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();
}
}
}

@ -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

@ -11,6 +11,8 @@ import com.jme3.terrain.heightmap.HeightMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import com.jme3.material.Material; import com.jme3.material.Material;
@ -35,6 +37,37 @@ public class TerrainGrid extends TerrainQuad {
private Vector3f[] quadIndex; private Vector3f[] quadIndex;
private Map<String, TerrainGridListener> listeners = new HashMap<String, TerrainGridListener>(); private Map<String, TerrainGridListener> listeners = new HashMap<String, TerrainGridListener>();
private Material material; 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, public TerrainGrid(String name, int patchSize, int size, Vector3f stepScale, HeightMapGrid heightMapGrid, int totalSize,
Vector2f offset, float offsetAmount, LodCalculatorFactory lodCalculatorFactory) { 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), 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); updateChildrens(Vector3f.ZERO);
} }
@ -93,7 +130,7 @@ public class TerrainGrid extends TerrainQuad {
} }
} }
//super.update(locations); super.update(locations);
} }
public Vector3f getCell(Vector3f location) { public Vector3f getCell(Vector3f location) {
@ -102,8 +139,10 @@ public class TerrainGrid extends TerrainQuad {
} }
protected void removeQuad(int idx) { protected void removeQuad(int idx) {
if (this.getQuad(idx) != null) {
this.detachChild(this.getQuad(idx)); this.detachChild(this.getQuad(idx));
} }
}
protected void moveQuad(int from, int to) { protected void moveQuad(int from, int to) {
this.removeQuad(to); this.removeQuad(to);
@ -112,16 +151,66 @@ public class TerrainGrid extends TerrainQuad {
fq.setLocalTranslation(this.quadOrigins[to - 1]); fq.setLocalTranslation(this.quadOrigins[to - 1]);
} }
protected TerrainQuad createQuadAt(Vector3f location, int quadrant) { protected void attachQuadAt(TerrainQuad q, 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]);
q.setMaterial(this.material); q.setMaterial(this.material);
q.setLocalTranslation(quadOrigins[quadrant - 1]);
q.setQuadrant((short) quadrant); q.setQuadrant((short) quadrant);
return q; this.attachChild(q);
} }
private void updateChildrens(Vector3f cam) { 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); RigidBodyControl control = getControl(RigidBodyControl.class);
PhysicsSpace space = null; PhysicsSpace space = null;
if (control != null) { if (control != null) {
@ -129,59 +218,16 @@ public class TerrainGrid extends TerrainQuad {
space.remove(this); space.remove(this);
this.removeControl(control); 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(1);
this.removeQuad(2); this.removeQuad(2);
this.removeQuad(3); this.removeQuad(3);
this.removeQuad(4); this.removeQuad(4);
} attachQuadAt(q1, 1);
this.attachChild(this.createQuadAt(cam.add(this.quadIndex[0]).mult(this.quadSize - 1), 1)); attachQuadAt(q2, 2);
this.attachChild(this.createQuadAt(cam.add(this.quadIndex[1]).mult(this.quadSize - 1), 2)); attachQuadAt(q3, 3);
this.attachChild(this.createQuadAt(cam.add(this.quadIndex[2]).mult(this.quadSize - 1), 3)); attachQuadAt(q4, 4);
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.currentCell = cam; this.currentCell = cam;
this.setLocalTranslation(cam.mult(2 * this.quadSize)); this.setLocalTranslation(cam.mult(2 * this.quadSize));

@ -29,7 +29,6 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.terrain.geomipmap; package com.jme3.terrain.geomipmap;
import com.jme3.export.InputCapsule; import com.jme3.export.InputCapsule;
@ -83,7 +82,6 @@ public class TerrainLodControl extends AbstractControl {
@Override @Override
protected void controlRender(RenderManager rm, ViewPort vp) { protected void controlRender(RenderManager rm, ViewPort vp) {
} }
@Override @Override
@ -93,8 +91,10 @@ public class TerrainLodControl extends AbstractControl {
if (cameras != null) { if (cameras != null) {
if (cameraLocations.isEmpty() && !cameras.isEmpty()) { if (cameraLocations.isEmpty() && !cameras.isEmpty()) {
for (Camera c : cameras) // populate them for (Camera c : cameras) // populate them
{
cameraLocations.add(c.getLocation()); cameraLocations.add(c.getLocation());
} }
}
terrain.update(cameraLocations); terrain.update(cameraLocations);
} }
} }
@ -103,28 +103,30 @@ public class TerrainLodControl extends AbstractControl {
if (spatial instanceof Terrain) { if (spatial instanceof Terrain) {
List<Camera> cameraClone = new ArrayList<Camera>(); List<Camera> cameraClone = new ArrayList<Camera>();
if (cameras != null) { if (cameras != null) {
for (Camera c : cameras) for (Camera c : cameras) {
cameraClone.add(c); 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) { public void setCameras(List<Camera> cameras) {
this.cameras = cameras; this.cameras = cameras;
cameraLocations.clear(); cameraLocations.clear();
for (Camera c : cameras) for (Camera c : cameras) {
cameraLocations.add(c.getLocation()); cameraLocations.add(c.getLocation());
} }
}
@Override @Override
public void setSpatial(Spatial spatial) { public void setSpatial(Spatial spatial) {
super.setSpatial(spatial); super.setSpatial(spatial);
if (spatial instanceof TerrainQuad) if (spatial instanceof TerrainQuad) {
this.terrain = (TerrainQuad) spatial; this.terrain = (TerrainQuad) spatial;
} }
}
public void setTerrain(TerrainQuad terrain) { public void setTerrain(TerrainQuad terrain) {
this.terrain = terrain; this.terrain = terrain;

@ -108,7 +108,7 @@ public class TerrainQuad extends Node implements Terrain {
private TerrainPicker picker; private TerrainPicker picker;
private ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() { protected ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
public Thread newThread(Runnable r) { public Thread newThread(Runnable r) {
Thread th = new Thread(r); Thread th = new Thread(r);
th.setDaemon(true); th.setDaemon(true);

@ -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;
}
}

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

@ -20,9 +20,18 @@ import com.jme3.renderer.Camera;
import com.jme3.terrain.geomipmap.TerrainGrid; import com.jme3.terrain.geomipmap.TerrainGrid;
import com.jme3.terrain.geomipmap.TerrainLodControl; import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad; import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.FractalHeightMapGrid;
import com.jme3.terrain.heightmap.ImageBasedHeightMapGrid; import com.jme3.terrain.heightmap.ImageBasedHeightMapGrid;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode; 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 { public class TerrainGridTest extends SimpleApplication {
@ -31,13 +40,18 @@ public class TerrainGridTest extends SimpleApplication {
private float grassScale = 64; private float grassScale = 64;
private float dirtScale = 16; private float dirtScale = 16;
private float rockScale = 128; private float rockScale = 128;
private boolean usePhysics = false; private boolean usePhysics = true;
public static void main(final String[] args) { public static void main(final String[] args) {
TerrainGridTest app = new TerrainGridTest(); TerrainGridTest app = new TerrainGridTest();
app.start(); app.start();
} }
private CharacterControl player3; private CharacterControl player3;
private FractalSum base;
private PerturbFilter perturb;
private OptimizedErode therm;
private SmoothFilter smooth;
private IterativeFilter iterate;
@Override @Override
public void simpleInitApp() { public void simpleInitApp() {
@ -70,8 +84,43 @@ public class TerrainGridTest extends SimpleApplication {
mat_terrain.setTexture("Tex3", rock); mat_terrain.setTexture("Tex3", rock);
mat_terrain.setFloat("Tex3Scale", rockScale); mat_terrain.setFloat("Tex3Scale", rockScale);
this.terrain = new TerrainGrid("terrain", 65, 1025, new ImageBasedHeightMapGrid("Textures/Terrain/grid/mountains", "png", this.base = new FractalSum();
this.assetManager)); 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.setMaterial(this.mat_terrain);
this.terrain.setLocalTranslation(0, 0, 0); this.terrain.setLocalTranslation(0, 0, 0);

Loading…
Cancel
Save