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. 225
      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. 168
      engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGrid.java
  7. 54
      engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java
  8. 138
      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

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

@ -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,7 +139,9 @@ public class TerrainGrid extends TerrainQuad {
} }
protected void removeQuad(int idx) { 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) { protected void moveQuad(int from, int 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; this.removeQuad(1);
if (this.currentCell != null) { this.removeQuad(2);
dx -= (int) (this.currentCell.x); this.removeQuad(3);
dz -= (int) (this.currentCell.z); this.removeQuad(4);
} attachQuadAt(q1, 1);
if (this.currentCell == null || FastMath.abs(dx) > 1 || FastMath.abs(dz) > 1 || (dx != 0 && dz != 0)) { attachQuadAt(q2, 2);
if (this.currentCell != null) { attachQuadAt(q3, 3);
// in case of teleport, otherwise the FastMath.abs(delta) should attachQuadAt(q4, 4);
// 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.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;
@ -57,7 +56,7 @@ import java.util.ArrayList;
* NOTE: right now it just uses the first camera passed in, * NOTE: right now it just uses the first camera passed in,
* in the future it will use all of them to determine what * in the future it will use all of them to determine what
* LOD to set. * LOD to set.
* *
* @author Brent Owens * @author Brent Owens
*/ */
public class TerrainLodControl extends AbstractControl { public class TerrainLodControl extends AbstractControl {
@ -68,8 +67,8 @@ public class TerrainLodControl extends AbstractControl {
public TerrainLodControl() { public TerrainLodControl() {
} }
/** /**
* Only uses the first camera right now. * Only uses the first camera right now.
* @param terrain to act upon (must be a Spatial) * @param terrain to act upon (must be a Spatial)
* @param cameras one or more cameras to reference for LOD calc * @param cameras one or more cameras to reference for LOD calc
@ -80,52 +79,55 @@ public class TerrainLodControl extends AbstractControl {
} }
this.cameras = cameras; this.cameras = cameras;
} }
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
@Override @Override
protected void controlUpdate(float tpf) { protected void controlRender(RenderManager rm, ViewPort vp) {
//list of cameras for when terrain supports multiple cameras (ie split screen) }
@Override
protected void controlUpdate(float tpf) {
//list of cameras for when terrain supports multiple cameras (ie split screen)
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);
} }
} }
public Control cloneForSpatial(Spatial spatial) { public Control cloneForSpatial(Spatial spatial) {
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;
} }

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

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