diff --git a/engine/lib/nblibraries.properties b/engine/lib/nblibraries.properties index f490864b7..d64748a85 100644 --- a/engine/lib/nblibraries.properties +++ b/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 diff --git a/engine/lib/noise/noise-0.0.1-SNAPSHOT.jar b/engine/lib/noise/noise-0.0.1-SNAPSHOT.jar new file mode 100644 index 000000000..62dc1f17d Binary files /dev/null and b/engine/lib/noise/noise-0.0.1-SNAPSHOT.jar differ diff --git a/engine/nbproject/project.properties b/engine/nbproject/project.properties index fbee1d131..876511762 100644 --- a/engine/nbproject/project.properties +++ b/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 diff --git a/engine/src/terrain/com/jme3/terrain/MapUtils.java b/engine/src/terrain/com/jme3/terrain/MapUtils.java new file mode 100644 index 000000000..192ebf979 --- /dev/null +++ b/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(); + } + } +} diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/LRUCache.java b/engine/src/terrain/com/jme3/terrain/geomipmap/LRUCache.java new file mode 100644 index 000000000..81f2c5641 --- /dev/null +++ b/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 LinkedHashMap. + * + *

+ * This cache has a fixed maximum number of elements (cacheSize). + * If the cache is full and another entry is added, the LRU (least recently + * used) entry is dropped. + * + *

+ * This class is thread-safe. All methods of this class are synchronized. + * + *

+ * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
+ * Multi-licensed: EPL / LGPL / GPL / AL / BSD. + */ +public class LRUCache { + + private static final float hashTableLoadFactor = 0.75f; + + private LinkedHashMap 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(hashTableCapacity, LRUCache.hashTableLoadFactor, true) { + // (an anonymous inner class) + private static final long serialVersionUID = 1; + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return this.size() > LRUCache.this.cacheSize; + } + }; + } + + /** + * Retrieves an entry from the cache.
+ * 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 Collection that contains a copy of all cache + * entries. + * + * @return a Collection with a copy of the cache content. + */ + public synchronized Collection> getAll() { + return new ArrayList>(this.map.entrySet()); + } + +} // end class LRUCache diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGrid.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGrid.java index bb3f3d69c..52d7db166 100644 --- a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGrid.java +++ b/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 listeners = new HashMap(); private Material material; + private LRUCache cache = new LRUCache(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 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)); diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java index 17139414b..9cd2c82c2 100644 --- a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java +++ b/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 cameraClone = new ArrayList(); 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 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; } diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java index 998b70351..30af02967 100644 --- a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java +++ b/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 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 camLocations; - + UpdateLOD(List 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 updated = new HashMap(); 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 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 location, HashMap 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 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 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 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