diff --git a/Test Results - TerrainQuadTest.html b/Test Results - TerrainQuadTest.html new file mode 100644 index 0000000000..b4271d9d9a --- /dev/null +++ b/Test Results - TerrainQuadTest.html @@ -0,0 +1,619 @@ + + + + +Test Results — TerrainQuadTest + + + + + + + + + +
+ +
+ +
+
+ + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a0f68906bf..f76418f51d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Dec 01 20:04:11 EST 2014 +#Wed Mar 16 12:05:41 CET 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip diff --git a/jme3-core/src/main/resources/com/jme3/system/version.properties b/jme3-core/src/main/resources/com/jme3/system/version.properties index 98168a14e1..0aa73859a9 100644 --- a/jme3-core/src/main/resources/com/jme3/system/version.properties +++ b/jme3-core/src/main/resources/com/jme3/system/version.properties @@ -1,11 +1,12 @@ # THIS IS AN AUTO-GENERATED FILE.. # DO NOT MODIFY! -build.date=1900-01-01 -git.revision=0 -git.branch=unknown -git.hash= -git.hash.short= -git.tag= -name.full=jMonkeyEngine 3.1.0-UNKNOWN +build.date=2016-03-16 +git.revision=5475 +git.branch=refactorings +git.hash=10947e8b5096f6d7e2bf0e327faa43b275adeb34 +git.hash.short=10947e8 +git.tag=null +name.full=jMonkeyEngine 3.1-refactorings-5475 +version.full=3.1-refactorings-5475 version.number=3.1.0 version.tag=SNAPSHOT \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainTest.java index 7eff18b8f9..a0f6572351 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainTest.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainTest.java @@ -157,13 +157,14 @@ public void simpleInitApp() { */ terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); - control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier + control.setLodCalculator(new DistanceLodCalculator(65, 2.7f)); // patch size, and a multiplier terrain.addControl(control); terrain.setMaterial(matRock); terrain.setLocalTranslation(0, -100, 0); terrain.setLocalScale(2f, 0.5f, 2f); rootNode.attachChild(terrain); + DirectionalLight light = new DirectionalLight(); light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize()); rootNode.addLight(light); @@ -187,6 +188,7 @@ private void setupKeys() { inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P)); inputManager.addListener(actionListener, "triPlanar"); } + private ActionListener actionListener = new ActionListener() { public void onAction(String name, boolean pressed, float tpf) { diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java index 995bc8f132..8733716643 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java @@ -35,20 +35,21 @@ import com.jme3.renderer.Camera; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * An extension of the TerrainLodControl that handles - * multiple terrains at once. This is to be used if you + * multiple terrains at once. This is to be used if you * have your own tiling/paging terrain system, such as * TerrainGrid. - * + * * @author Brent Owens */ public class MultiTerrainLodControl extends TerrainLodControl { - + List terrains = new ArrayList(); private List addedTerrains = new ArrayList(); private List removedTerrains = new ArrayList(); @@ -64,7 +65,7 @@ public MultiTerrainLodControl(Camera camera) { this.cameras = cams; lodCalculator = new DistanceLodCalculator(65, 2.7f); } - + /** * Add a terrain that will have its LOD handled by this control. * It will be added next update run. You should only call this from @@ -73,7 +74,7 @@ public MultiTerrainLodControl(Camera camera) { public void addTerrain(TerrainQuad tq) { addedTerrains.add(tq); } - + /** * Add a terrain that will no longer have its LOD handled by this control. * It will be removed next update run. You should only call this from @@ -82,12 +83,12 @@ public void addTerrain(TerrainQuad tq) { public void removeTerrain(TerrainQuad tq) { removedTerrains.add(tq); } - + @Override protected UpdateLOD getLodThread(List locations, LodCalculator lodCalculator) { return new UpdateMultiLOD(locations, lodCalculator); } - + @Override protected void prepareTerrain() { if (!addedTerrains.isEmpty()) { @@ -97,57 +98,57 @@ protected void prepareTerrain() { } addedTerrains.clear(); } - + if (!removedTerrains.isEmpty()) { terrains.removeAll(removedTerrains); removedTerrains.clear(); } - + for (TerrainQuad terrain : terrains) terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely } - + /** * Overrides the parent UpdateLOD runnable to process * multiple terrains. */ protected class UpdateMultiLOD extends UpdateLOD { - - + + protected UpdateMultiLOD(List camLocations, LodCalculator lodCalculator) { super(camLocations, lodCalculator); } - + @Override public HashMap call() throws Exception { - + setLodCalcRunning(true); - - HashMap updated = new HashMap(); - + + HashMap updated = new HashMap(); + for (TerrainQuad terrainQuad : terrains) { // go through each patch and calculate its LOD based on camera distance - terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here + terrainQuad.hasLodChanged(camLocations, updated, lodCalculator); // 'updated' gets populated here } - + for (TerrainQuad terrainQuad : terrains) { // then calculate the neighbour LOD values for seaming terrainQuad.findNeighboursLod(updated); } - + for (TerrainQuad terrainQuad : terrains) { // check neighbour quads that need their edges seamed terrainQuad.fixEdges(updated); } - + for (TerrainQuad terrainQuad : terrains) { // perform the edge seaming, if it requires it terrainQuad.reIndexPages(updated, lodCalculator.usesVariableLod()); } - + //setUpdateQuadLODs(updated); // set back to main ogl thread setLodCalcRunning(false); - + return updated; } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java index 8ad2425ada..e17b37dcfa 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java @@ -46,16 +46,12 @@ import com.jme3.terrain.Terrain; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; + import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -68,16 +64,16 @@ * 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. - * + *

* This control serializes, but it does not save the Camera reference. * This camera reference has to be manually added in when you load the * terrain to the scene! - * + *

* When the control or the terrain are removed from the scene, you should call * TerrainLodControl.detachAndCleanUpControl() to remove any threads it created * to handle the LOD processing. If you supply your own executor service, then * you have to handle its thread termination yourself. - * + * * @author Brent Owens */ public class TerrainLodControl extends AbstractControl { @@ -88,17 +84,17 @@ public class TerrainLodControl extends AbstractControl { protected LodCalculator lodCalculator; private boolean hasResetLod = false; // used when enabled is set to false - private HashMap updatedPatches; + private HashMap updatedPatches; private final Object updatePatchesLock = new Object(); - + protected List lastCameraLocations; // used for LOD calc private AtomicBoolean lodCalcRunning = new AtomicBoolean(false); private int lodOffCount = 0; - + protected ExecutorService executor; protected Future> indexer; private boolean forceUpdate = true; - + public TerrainLodControl() { } @@ -109,9 +105,10 @@ public TerrainLodControl(Terrain terrain, Camera camera) { this.cameras = cams; lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator } - + /** * 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 */ @@ -132,7 +129,7 @@ protected void controlRender(RenderManager rm, ViewPort vp) { public void setExecutor(ExecutorService executor) { this.executor = executor; } - + protected ExecutorService createExecutorService() { return Executors.newSingleThreadExecutor(new ThreadFactory() { public Thread newThread(Runnable r) { @@ -143,14 +140,14 @@ public Thread newThread(Runnable r) { } }); } - + @Override protected void controlUpdate(float tpf) { //list of cameras for when terrain supports multiple cameras (ie split screen) if (lodCalculator == null) return; - + if (!enabled) { if (!hasResetLod) { // this will get run once @@ -158,7 +155,7 @@ protected void controlUpdate(float tpf) { lodCalculator.turnOffLod(); } } - + if (cameras != null) { cameraLocations.clear(); for (Camera c : cameras) // populate them @@ -168,7 +165,7 @@ protected void controlUpdate(float tpf) { updateLOD(cameraLocations, lodCalculator); } } - + /** * Call this when you remove the terrain or this control from the scene. * It will clear up any threads it had. @@ -181,10 +178,10 @@ public void detachAndCleanUpControl() { // do all of the LOD calculations protected void updateLOD(List locations, LodCalculator lodCalculator) { - if(getSpatial() == null){ + if (getSpatial() == null) { return; } - + // update any existing ones that need updating updateQuadLODs(); @@ -194,17 +191,16 @@ protected void updateLOD(List locations, LodCalculator lodCalculator) return; else lodOffCount++; - } else + } else lodOffCount = 0; - + if (lastCameraLocations != null) { if (!forceUpdate && lastCameraLocationsTheSame(locations) && !lodCalculator.isLodOff()) return; // don't update if in same spot else lastCameraLocations = cloneVectorList(locations); forceUpdate = false; - } - else { + } else { lastCameraLocations = cloneVectorList(locations); return; } @@ -216,9 +212,9 @@ protected void updateLOD(List locations, LodCalculator lodCalculator) if (executor == null) executor = createExecutorService(); - + prepareTerrain(); - + UpdateLOD updateLodThread = getLodThread(locations, lodCalculator); indexer = executor.submit(updateLodThread); } @@ -230,12 +226,12 @@ protected void updateLOD(List locations, LodCalculator lodCalculator) public void forceUpdate() { this.forceUpdate = true; } - + protected void prepareTerrain() { - TerrainQuad terrain = (TerrainQuad)getSpatial(); + TerrainQuad terrain = (TerrainQuad) getSpatial(); terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely } - + protected UpdateLOD getLodThread(List locations, LodCalculator lodCalculator) { return new UpdateLOD(locations, lodCalculator); } @@ -247,7 +243,7 @@ private void updateQuadLODs() { if (indexer != null) { if (indexer.isDone()) { try { - + HashMap updated = indexer.get(); if (updated != null) { // do the actual geometry update here @@ -255,7 +251,7 @@ private void updateQuadLODs() { utp.updateAll(); } } - + } catch (InterruptedException ex) { Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex); } catch (ExecutionException ex) { @@ -266,12 +262,12 @@ private void updateQuadLODs() { } } } - + private boolean lastCameraLocationsTheSame(List locations) { boolean theSame = true; for (Vector3f l : locations) { for (Vector3f v : lastCameraLocations) { - if (!v.equals(l) ) { + if (!v.equals(l)) { theSame = false; return false; } @@ -279,7 +275,7 @@ private boolean lastCameraLocationsTheSame(List locations) { } return theSame; } - + protected synchronized boolean isLodCalcRunning() { return lodCalcRunning.get(); } @@ -290,17 +286,12 @@ protected synchronized void setLodCalcRunning(boolean running) { private List cloneVectorList(List locations) { List cloned = new ArrayList(); - for(Vector3f l : locations) + for (Vector3f l : locations) cloned.add(l.clone()); return cloned; } - - - - - - + public Control cloneForSpatial(Spatial spatial) { if (spatial instanceof Terrain) { List cameraClone = new ArrayList(); @@ -321,7 +312,7 @@ public void setCamera(Camera camera) { cams.add(camera); setCameras(cams); } - + public void setCameras(List cameras) { this.cameras = cameras; cameraLocations.clear(); @@ -349,7 +340,7 @@ public LodCalculator getLodCalculator() { public void setLodCalculator(LodCalculator lodCalculator) { this.lodCalculator = lodCalculator; } - + @Override public void setEnabled(boolean enabled) { this.enabled = enabled; @@ -361,12 +352,12 @@ public void setEnabled(boolean enabled) { lodCalculator.turnOnLod(); } } - - + + /** * Calculates the LOD of all child terrain patches. */ - protected class UpdateLOD implements Callable> { + protected class UpdateLOD implements Callable> { protected List camLocations; protected LodCalculator lodCalculator; @@ -382,19 +373,19 @@ public HashMap call() throws Exception { //} setLodCalcRunning(true); - TerrainQuad terrainQuad = (TerrainQuad)getSpatial(); - + TerrainQuad terrainQuad = (TerrainQuad) getSpatial(); + // go through each patch and calculate its LOD based on camera distance - HashMap updated = new HashMap(); - boolean lodChanged = terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here + HashMap updated = new HashMap(); + boolean lodChanged = terrainQuad.hasLodChanged(camLocations, updated, lodCalculator); // 'updated' gets populated here if (!lodChanged) { // not worth updating anything else since no one's LOD changed setLodCalcRunning(false); return null; } - - + + // then calculate its neighbour LOD values for seaming in the shader terrainQuad.findNeighboursLod(updated); @@ -405,7 +396,7 @@ public HashMap call() throws Exception { //setUpdateQuadLODs(updated); // set back to main ogl thread setLodCalcRunning(false); - + return updated; } } @@ -414,7 +405,7 @@ public HashMap call() throws Exception { public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); - oc.write((Node)terrain, "terrain", null); + oc.write((Node) terrain, "terrain", null); oc.write(lodCalculator, "lodCalculator", null); } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java index 4ac811e9fc..ed09cd3df6 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java @@ -50,6 +50,7 @@ import com.jme3.terrain.geomipmap.TerrainQuad.LocationHeight; import com.jme3.terrain.geomipmap.lodcalc.util.EntropyComputeUtil; import com.jme3.util.BufferUtils; + import java.io.IOException; import java.nio.Buffer; import java.nio.FloatBuffer; @@ -65,18 +66,18 @@ * That uses a geo-mipmapping algorithm to change the index buffer of the mesh. * The mesh is a triangle strip. In wireframe mode you might notice some strange lines, these are degenerate * triangles generated by the geoMipMap algorithm and can be ignored. The video card removes them at almost no cost. - * + *

* Each patch needs to know its neighbour's LOD so it can seam its edges with them, in case the neighbour has a different * LOD. If this doesn't happen, you will see gaps. - * + *

* The LOD value is most detailed at zero. It gets less detailed the higher the LOD value until you reach maxLod, which * is a mathematical limit on the number of times the 'size' of the patch can be divided by two. However there is a -1 to that * for now until I add in a custom index buffer calculation for that max level, the current algorithm does not go that far. - * - * You can supply a LodThresholdCalculator for use in determining when the LOD should change. It's API will no doubt change - * in the near future. Right now it defaults to just changing LOD every two patch sizes. So if a patch has a size of 65, + *

+ * You can supply a LodThresholdCalculator for use in determining when the LOD should change. It's API will no doubt change + * in the near future. Right now it defaults to just changing LOD every two patch sizes. So if a patch has a size of 65, * then the LOD changes every 130 units away. - * + * * @author Brent Owens */ public class TerrainPatch extends Geometry { @@ -114,18 +115,20 @@ public class TerrainPatch extends Geometry { protected float[] lodEntropy; + private final int DIR_RIGHT = 0, DIR_DOWN = 1, DIR_LEFT = 2, DIR_TOP = 3; + public TerrainPatch() { super("TerrainPatch"); setBatchHint(BatchHint.Never); } - + public TerrainPatch(String name) { super(name); setBatchHint(BatchHint.Never); } public TerrainPatch(String name, int size) { - this(name, size, new Vector3f(1,1,1), null, new Vector3f(0,0,0)); + this(name, size, new Vector3f(1, 1, 1), null, new Vector3f(0, 0, 0)); } /** @@ -133,19 +136,14 @@ public TerrainPatch(String name, int size) { * parameters and heightmap data are then processed to generate a * TriMesh object for rendering. * - * @param name - * the name of the terrain patch. - * @param size - * the size of the heightmap. - * @param stepScale - * the scale for the axes. - * @param heightMap - * the height data. - * @param origin - * the origin offset of the patch. + * @param name the name of the terrain patch. + * @param size the size of the heightmap. + * @param stepScale the scale for the axes. + * @param heightMap the height data. + * @param origin the origin offset of the patch. */ public TerrainPatch(String name, int size, Vector3f stepScale, - float[] heightMap, Vector3f origin) { + float[] heightMap, Vector3f origin) { this(name, size, stepScale, heightMap, origin, size, new Vector2f(), 0); } @@ -154,27 +152,19 @@ public TerrainPatch(String name, int size, Vector3f stepScale, * parameters and heightmap data are then processed to generate a * TriMesh object for renderering. * - * @param name - * the name of the terrain patch. - * @param size - * the size of the patch. - * @param stepScale - * the scale for the axes. - * @param heightMap - * the height data. - * @param origin - * the origin offset of the patch. - * @param totalSize - * the total size of the terrain. (Higher if the patch is part of - * a TerrainQuad tree. - * @param offset - * the offset for texture coordinates. - * @param offsetAmount - * the total offset amount. Used for texture coordinates. + * @param name the name of the terrain patch. + * @param size the size of the patch. + * @param stepScale the scale for the axes. + * @param heightMap the height data. + * @param origin the origin offset of the patch. + * @param totalSize the total size of the terrain. (Higher if the patch is part of + * a TerrainQuad tree. + * @param offset the offset for texture coordinates. + * @param offsetAmount the total offset amount. Used for texture coordinates. */ public TerrainPatch(String name, int size, Vector3f stepScale, - float[] heightMap, Vector3f origin, int totalSize, - Vector2f offset, float offsetAmount) { + float[] heightMap, Vector3f origin, int totalSize, + Vector2f offset, float offsetAmount) { super(name); setBatchHint(BatchHint.Never); this.size = size; @@ -186,7 +176,7 @@ public TerrainPatch(String name, int size, Vector3f stepScale, setLocalTranslation(origin); geomap = new LODGeomap(size, heightMap); - Mesh m = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false); + Mesh m = geomap.createMesh(stepScale, new Vector2f(1, 1), offset, offsetAmount, totalSize, false); setMesh(m); } @@ -195,23 +185,23 @@ public TerrainPatch(String name, int size, Vector3f stepScale, * This calculation is slow, so don't use it often. */ public void generateLodEntropies() { - float[] entropies = new float[getMaxLod()+1]; - for (int i = 0; i <= getMaxLod(); i++){ + float[] entropies = new float[getMaxLod() + 1]; + for (int i = 0; i <= getMaxLod(); i++) { int curLod = (int) Math.pow(2, i); IndexBuffer idxB = geomap.writeIndexArrayLodDiff(curLod, false, false, false, false, totalSize); Buffer ib; if (idxB.getBuffer() instanceof IntBuffer) - ib = (IntBuffer)idxB.getBuffer(); + ib = (IntBuffer) idxB.getBuffer(); else - ib = (ShortBuffer)idxB.getBuffer(); + ib = (ShortBuffer) idxB.getBuffer(); entropies[i] = EntropyComputeUtil.computeLodEntropy(mesh, ib); } lodEntropy = entropies; } - public float[] getLodEntropies(){ - if (lodEntropy == null){ + public float[] getLodEntropies() { + if (lodEntropy == null) { generateLodEntropies(); } return lodEntropy; @@ -221,7 +211,7 @@ public float[] getLodEntropies(){ public FloatBuffer getHeightmap() { return BufferUtils.createFloatBuffer(geomap.getHeightArray()); } - + public float[] getHeightMap() { return geomap.getHeightArray(); } @@ -231,20 +221,21 @@ public float[] getHeightMap() { * If the patch size is 32 then the returned value would be log2(32)-2 = 3 * You can then use that value, 3, to see how many times you can divide 32 by 2 * before the terrain gets too un-detailed (can't stitch it any further). + * * @return the maximum LOD */ public int getMaxLod() { if (maxLod < 0) - maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide + maxLod = Math.max(1, (int) (FastMath.log(size - 1) / FastMath.log(2)) - 1); // -1 forces our minimum of 4 triangles wide return maxLod; } - protected void reIndexGeometry(HashMap updated, boolean useVariableLod) { + protected void reIndexGeometry(HashMap updated, boolean useVariableLod) { UpdatedTerrainPatch utp = updated.get(getName()); - if (utp != null && utp.isReIndexNeeded() ) { + if (utp != null && utp.isReIndexNeeded()) { int pow = (int) Math.pow(2, utp.getNewLod()); boolean left = utp.getLeftLod() > utp.getNewLod(); boolean top = utp.getTopLod() > utp.getNewLod(); @@ -256,12 +247,12 @@ protected void reIndexGeometry(HashMap updated, bool idxB = geomap.writeIndexArrayLodVariable(pow, (int) Math.pow(2, utp.getRightLod()), (int) Math.pow(2, utp.getTopLod()), (int) Math.pow(2, utp.getLeftLod()), (int) Math.pow(2, utp.getBottomLod()), totalSize); else idxB = geomap.writeIndexArrayLodDiff(pow, right, top, left, bottom, totalSize); - + Buffer b; if (idxB.getBuffer() instanceof IntBuffer) - b = (IntBuffer)idxB.getBuffer(); + b = (IntBuffer) idxB.getBuffer(); else - b = (ShortBuffer)idxB.getBuffer(); + b = (ShortBuffer) idxB.getBuffer(); utp.setNewIndexBuffer(b); } @@ -274,39 +265,140 @@ public Vector2f getTex(float x, float z, Vector2f store) { return store; } int idx = (int) (z * size + x); - return store.set(getMesh().getFloatBuffer(Type.TexCoord).get(idx*2), - getMesh().getFloatBuffer(Type.TexCoord).get(idx*2+1) ); + return store.set(getMesh().getFloatBuffer(Type.TexCoord).get(idx * 2), + getMesh().getFloatBuffer(Type.TexCoord).get(idx * 2 + 1)); } - + public float getHeightmapHeight(float x, float z) { if (x < 0 || z < 0 || x >= size || z >= size) return 0; int idx = (int) (z * size + x); - return getMesh().getFloatBuffer(Type.Position).get(idx*3+1); // 3 floats per entry (x,y,z), the +1 is to get the Y + return getMesh().getFloatBuffer(Type.Position).get(idx * 3 + 1); // 3 floats per entry (x,y,z), the +1 is to get the Y } - + /** * Get the triangle of this geometry at the specified local coordinate. + * * @param x local to the terrain patch * @param z local to the terrain patch * @return the triangle in world coordinates, or null if the point does intersect this patch on the XZ axis */ public Triangle getTriangle(float x, float z) { - return geomap.getTriangleAtPoint(x, z, getWorldScale() , getWorldTranslation()); + return geomap.getTriangleAtPoint(x, z, getWorldScale(), getWorldTranslation()); } /** * Get the triangles at the specified grid point. Probably only 2 triangles + * * @param x local to the terrain patch * @param z local to the terrain patch * @return the triangles in world coordinates, or null if the point does intersect this patch on the XZ axis */ public Triangle[] getGridTriangles(float x, float z) { - return geomap.getGridTrianglesAtPoint(x, z, getWorldScale() , getWorldTranslation()); + return geomap.getGridTrianglesAtPoint(x, z, getWorldScale(), getWorldTranslation()); + } + + protected TerrainPatch findPatch(int direction) { + switch (direction) { + case DIR_RIGHT : return getRightNeighbourPatch(); + case DIR_DOWN : return getDownNeighbourPatch(); + case DIR_LEFT : return getLeftNeighbourPatch(); + case DIR_TOP : return getTopNeighbourPatch(); + } + + return null; + } + + private TerrainPatch getRightNeighbourPatch() { + TerrainQuad parent = (TerrainQuad) this.getParent(); + TerrainQuad neighbourQuad; + + switch(quadrant) { + case 1: return parent.getPatch(3); + case 2: return parent.getPatch(4); + case 3: + neighbourQuad = parent.findQuad(DIR_RIGHT); + if (neighbourQuad != null) + return neighbourQuad.getPatch(1); + break; + case 4: + neighbourQuad = parent.findQuad(DIR_RIGHT); + if (neighbourQuad != null) + return neighbourQuad.getPatch(2); + break; + } + + return null; + } + + private TerrainPatch getDownNeighbourPatch() { + TerrainQuad parent = (TerrainQuad) this.getParent(); + TerrainQuad neighbourQuad; + + switch(quadrant) { + case 1: return parent.getPatch(2); + case 2: + neighbourQuad = parent.findQuad(DIR_DOWN); + if (neighbourQuad != null) + return neighbourQuad.getPatch(1); + break; + case 3: return parent.getPatch(4); + case 4: + neighbourQuad = parent.findQuad(DIR_DOWN); + if (neighbourQuad != null) + return neighbourQuad.getPatch(3); + break; + } + + return null; + } + + private TerrainPatch getLeftNeighbourPatch() { + TerrainQuad parent = (TerrainQuad) this.getParent(); + TerrainQuad neighbourQuad; + + switch(quadrant) { + case 1: + neighbourQuad = parent.findQuad(DIR_LEFT); + if (neighbourQuad != null) + return neighbourQuad.getPatch(3); + break; + case 2: + neighbourQuad = parent.findQuad(DIR_LEFT); + if (neighbourQuad != null) + return neighbourQuad.getPatch(4); + break; + case 3: return parent.getPatch(1); + case 4: return parent.getPatch(2); + } + + return null; + } + + private TerrainPatch getTopNeighbourPatch() { + TerrainQuad parent = (TerrainQuad) this.getParent(); + TerrainQuad neighbourQuad; + + switch(quadrant) { + case 1: + neighbourQuad = parent.findQuad(DIR_TOP); + if (neighbourQuad != null) + return neighbourQuad.getPatch(2); + break; + case 2: return parent.getPatch(1); + case 3: + neighbourQuad = parent.findQuad(DIR_TOP); + if (neighbourQuad != null) + return neighbourQuad.getPatch(4); + break; + case 4: return parent.getPatch(3); + } + + return null; } protected void setHeight(List locationHeights, boolean overrideHeight) { - + for (LocationHeight lh : locationHeights) { if (lh.x < 0 || lh.z < 0 || lh.x >= size || lh.z >= size) continue; @@ -314,10 +406,10 @@ protected void setHeight(List locationHeights, boolean overrideH if (overrideHeight) { geomap.getHeightArray()[idx] = lh.h; } else { - float h = getMesh().getFloatBuffer(Type.Position).get(idx*3+1); - geomap.getHeightArray()[idx] = h+lh.h; + float h = getMesh().getFloatBuffer(Type.Position).get(idx * 3 + 1); + geomap.getHeightArray()[idx] = h + lh.h; } - + } FloatBuffer newVertexBuffer = geomap.writeVertexArray(null, stepScale, false); @@ -333,7 +425,7 @@ protected void updateNormals() { getMesh().getBuffer(Type.Normal).updateData(newNormalBuffer); FloatBuffer newTangentBuffer = null; FloatBuffer newBinormalBuffer = null; - FloatBuffer[] tb = geomap.writeTangentArray(newNormalBuffer, newTangentBuffer, newBinormalBuffer, (FloatBuffer)getMesh().getBuffer(Type.TexCoord).getData(), getWorldScale()); + FloatBuffer[] tb = geomap.writeTangentArray(newNormalBuffer, newTangentBuffer, newBinormalBuffer, (FloatBuffer) getMesh().getBuffer(Type.TexCoord).getData(), getWorldScale()); newTangentBuffer = tb[0]; newBinormalBuffer = tb[1]; getMesh().getBuffer(Type.Tangent).updateData(newTangentBuffer); @@ -344,39 +436,38 @@ private void setInBuffer(Mesh mesh, int index, Vector3f normal, Vector3f tangent VertexBuffer NB = mesh.getBuffer(Type.Normal); VertexBuffer TB = mesh.getBuffer(Type.Tangent); VertexBuffer BB = mesh.getBuffer(Type.Binormal); - BufferUtils.setInBuffer(normal, (FloatBuffer)NB.getData(), index); - BufferUtils.setInBuffer(tangent, (FloatBuffer)TB.getData(), index); - BufferUtils.setInBuffer(binormal, (FloatBuffer)BB.getData(), index); + BufferUtils.setInBuffer(normal, (FloatBuffer) NB.getData(), index); + BufferUtils.setInBuffer(tangent, (FloatBuffer) TB.getData(), index); + BufferUtils.setInBuffer(binormal, (FloatBuffer) BB.getData(), index); NB.setUpdateNeeded(); TB.setUpdateNeeded(); BB.setUpdateNeeded(); } - + /** * Matches the normals along the edge of the patch with the neighbours. * Computes the normals for the right, bottom, left, and top edges of the * patch, and saves those normals in the neighbour's edges too. - * + *

* Takes 4 points (if has neighbour on that side) for each * point on the edge of the patch: - * * - * | - * *---x---* - * | - * * - * It works across the right side of the patch, from the top down to + * * + * | + * *---x---* + * | + * * + * It works across the right side of the patch, from the top down to * the bottom. Then it works on the bottom side of the patch, from the * left to the right. */ protected void fixNormalEdges(TerrainPatch right, - TerrainPatch bottom, - TerrainPatch top, - TerrainPatch left, - TerrainPatch bottomRight, - TerrainPatch bottomLeft, - TerrainPatch topRight, - TerrainPatch topLeft) - { + TerrainPatch bottom, + TerrainPatch top, + TerrainPatch left, + TerrainPatch bottomRight, + TerrainPatch bottomLeft, + TerrainPatch topRight, + TerrainPatch topLeft) { Vector3f rootPoint = new Vector3f(); Vector3f rightPoint = new Vector3f(); Vector3f leftPoint = new Vector3f(); @@ -388,228 +479,283 @@ protected void fixNormalEdges(TerrainPatch right, Vector3f binormal = new Vector3f(); Vector3f normal = new Vector3f(); - - int s = this.getSize()-1; - + + int s = this.getSize() - 1; + if (right != null) { // right side, works its way down - for (int i=0; i= size || z >= size) return null; // out of range - - int index = (z*size+x)*3; - FloatBuffer nb = (FloatBuffer)this.getMesh().getBuffer(Type.Normal).getData(); + + int index = (z * size + x) * 3; + FloatBuffer nb = (FloatBuffer) this.getMesh().getBuffer(Type.Normal).getData(); Vector3f normal = new Vector3f(); normal.x = nb.get(index); - normal.y = nb.get(index+1); - normal.z = nb.get(index+2); + normal.y = nb.get(index + 1); + normal.z = nb.get(index + 2); return normal; } + public TerrainPatch[] getNeighbours() { + TerrainPatch[] neighbours = new TerrainPatch[4]; + neighbours[DIR_RIGHT] = this.rightNeighbour; + neighbours[DIR_DOWN] = this.bottomNeighbour; + neighbours[DIR_TOP] = this.topNeighbour; + neighbours[DIR_LEFT] = this.leftNeighbour; + return neighbours; + } + + /** + * createQuadPatch creates a terrain patch according to the provided patchNumber. + * + * @param terrainQuad + * @param patchNumber + * @param heightBlock + * @param quarterSize + * @param halfSize + * @param split + * @return TerrainPatch object + */ + protected TerrainPatch createQuadPatch(TerrainQuad terrainQuad, int patchNumber, float[] heightBlock, int quarterSize, int halfSize, int split) { + + Vector3f origin; + Vector2f tempOffset = new Vector2f(); + tempOffset.x = terrainQuad.offset.x; + tempOffset.y = terrainQuad.offset.y; + + switch (patchNumber) { + case 1: + origin = new Vector3f(-halfSize * terrainQuad.stepScale.x, 0, -halfSize + * terrainQuad.stepScale.z); + tempOffset.x += origin.x / 2; + tempOffset.y += origin.z / 2; + break; + case 2: + origin = new Vector3f(-halfSize * terrainQuad.stepScale.x, 0, 0); + tempOffset.x += origin.x / 2; + tempOffset.y += quarterSize * terrainQuad.stepScale.z; + break; + case 3: + origin = new Vector3f(0, 0, -halfSize * terrainQuad.stepScale.z); + tempOffset.x += quarterSize * terrainQuad.stepScale.x; + tempOffset.y += origin.z / 2; + break; + case 4: + origin = new Vector3f(0, 0, 0); + tempOffset.x += quarterSize * terrainQuad.stepScale.x; + tempOffset.y += quarterSize * terrainQuad.stepScale.z; + break; + default: + return null; + } + + return new TerrainPatch(terrainQuad.getName() + "Patch" + patchNumber, split, terrainQuad.stepScale, heightBlock, origin, terrainQuad.totalSize, tempOffset, terrainQuad.offsetAmount); + } + protected float getHeight(int x, int z, float xm, float zm) { - return geomap.getHeight(x,z,xm,zm); + return geomap.getHeight(x, z, xm, zm); } - + /** * Locks the mesh (sets it static) to improve performance. * But it it not editable then. Set unlock to make it editable. @@ -626,7 +772,7 @@ public void lockMesh() { public void unlockMesh() { getMesh().setDynamic(); } - + /** * Returns the offset amount this terrain patch uses for textures. * @@ -678,8 +824,7 @@ public Vector2f getOffset() { * coordinates. Note that this does NOT rebuild the terrain at all. * This is mostly used for outside constructors of terrain patches. * - * @param offset - * The new texture offset. + * @param offset The new texture offset. */ public void setOffset(Vector2f offset) { this.offset = offset; @@ -690,8 +835,7 @@ public void setOffset(Vector2f offset) { * rebuild the terrain at all. This is mostly used for outside constructors * of terrain patches. * - * @param size - * The new size. + * @param size The new size. */ public void setSize(int size) { this.size = size; @@ -704,8 +848,7 @@ public void setSize(int size) { * rebuild the terrain at all. This is mostly used for outside constructors * of terrain patches. * - * @param totalSize - * The new total size. + * @param totalSize The new total size. */ public void setTotalSize(int totalSize) { this.totalSize = totalSize; @@ -716,8 +859,7 @@ public void setTotalSize(int totalSize) { * does NOT rebuild the terrain at all. This is mostly used for * outside constructors of terrain patches. * - * @param stepScale - * The new step scale. + * @param stepScale The new step scale. */ public void setStepScale(Vector3f stepScale) { this.stepScale = stepScale; @@ -728,8 +870,7 @@ public void setStepScale(Vector3f stepScale) { * rebuild the terrain at all. This is mostly used for outside * constructors of terrain patches. * - * @param offsetAmount - * The new texture offset. + * @param offsetAmount The new texture offset. */ public void setOffsetAmount(float offsetAmount) { this.offsetAmount = offsetAmount; @@ -743,8 +884,7 @@ public short getQuadrant() { } /** - * @param quadrant - * The quadrant to set. + * @param quadrant The quadrant to set. */ public void setQuadrant(short quadrant) { this.quadrant = quadrant; @@ -807,18 +947,18 @@ protected void setLodBottom(int lodBottom) { public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException { if (refreshFlags != 0) throw new IllegalStateException("Scene graph must be updated" + - " before checking collision"); + " before checking collision"); if (other instanceof BoundingVolume) - if (!getWorldBound().intersects((BoundingVolume)other)) + if (!getWorldBound().intersects((BoundingVolume) other)) return 0; - - if(other instanceof Ray) - return collideWithRay((Ray)other, results); + + if (other instanceof Ray) + return collideWithRay((Ray) other, results); else if (other instanceof BoundingVolume) - return collideWithBoundingVolume((BoundingVolume)other, results); + return collideWithBoundingVolume((BoundingVolume) other, results); else { - throw new UnsupportedCollisionException("TerrainPatch cannnot collide with "+other.getClass().getName()); + throw new UnsupportedCollisionException("TerrainPatch cannnot collide with " + other.getClass().getName()); } } @@ -830,12 +970,12 @@ private int collideWithRay(Ray ray, CollisionResults results) { private int collideWithBoundingVolume(BoundingVolume boundingVolume, CollisionResults results) { if (boundingVolume instanceof BoundingBox) - return collideWithBoundingBox((BoundingBox)boundingVolume, results); - else if(boundingVolume instanceof BoundingSphere) { + return collideWithBoundingBox((BoundingBox) boundingVolume, results); + else if (boundingVolume instanceof BoundingSphere) { BoundingSphere sphere = (BoundingSphere) boundingVolume; BoundingBox bbox = new BoundingBox(boundingVolume.getCenter().clone(), sphere.getRadius(), - sphere.getRadius(), - sphere.getRadius()); + sphere.getRadius(), + sphere.getRadius()); return collideWithBoundingBox(bbox, results); } return 0; @@ -843,9 +983,9 @@ else if(boundingVolume instanceof BoundingSphere) { protected Vector3f worldCoordinateToLocal(Vector3f loc) { Vector3f translated = new Vector3f(); - translated.x = loc.x/getWorldScale().x - getWorldTranslation().x; - translated.y = loc.y/getWorldScale().y - getWorldTranslation().y; - translated.z = loc.z/getWorldScale().z - getWorldTranslation().z; + translated.x = loc.x / getWorldScale().x - getWorldTranslation().x; + translated.y = loc.y / getWorldScale().y - getWorldTranslation().y; + translated.z = loc.z / getWorldScale().z - getWorldTranslation().z; return translated; } @@ -853,12 +993,12 @@ protected Vector3f worldCoordinateToLocal(Vector3f loc) { * This most definitely is not optimized. */ private int collideWithBoundingBox(BoundingBox bbox, CollisionResults results) { - + // test the four corners, for cases where the bbox dimensions are less than the terrain grid size, which is probably most of the time - Vector3f topLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x-bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent())); - Vector3f topRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x+bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent())); - Vector3f bottomLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x-bbox.getXExtent(), 0, bbox.getCenter().z+bbox.getZExtent())); - Vector3f bottomRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x+bbox.getXExtent(), 0, bbox.getCenter().z+bbox.getZExtent())); + Vector3f topLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x - bbox.getXExtent(), 0, bbox.getCenter().z - bbox.getZExtent())); + Vector3f topRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x + bbox.getXExtent(), 0, bbox.getCenter().z - bbox.getZExtent())); + Vector3f bottomLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x - bbox.getXExtent(), 0, bbox.getCenter().z + bbox.getZExtent())); + Vector3f bottomRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x + bbox.getXExtent(), 0, bbox.getCenter().z + bbox.getZExtent())); Triangle t = getTriangle(topLeft.x, topLeft.z); if (t != null && bbox.collideWith(t, results) > 0) @@ -872,14 +1012,14 @@ private int collideWithBoundingBox(BoundingBox bbox, CollisionResults results) { t = getTriangle(bottomRight.x, bottomRight.z); if (t != null && bbox.collideWith(t, results) > 0) return 1; - + // box is larger than the points on the terrain, so test against the points - for (float z=topLeft.z; z= size || z >= size) continue; - t = getTriangle(x,z); + t = getTriangle(x, z); if (t != null && bbox.collideWith(t, results) > 0) return 1; } @@ -895,12 +1035,12 @@ public void write(JmeExporter ex) throws IOException { // this reduces the save size to 10% by not saving the mesh Mesh temp = getMesh(); mesh = null; - + super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(size, "size", 16); oc.write(totalSize, "totalSize", 16); - oc.write(quadrant, "quadrant", (short)0); + oc.write(quadrant, "quadrant", (short) 0); oc.write(stepScale, "stepScale", Vector3f.UNIT_XYZ); oc.write(offset, "offset", Vector3f.UNIT_XYZ); oc.write(offsetAmount, "offsetAmount", 0); @@ -908,7 +1048,7 @@ public void write(JmeExporter ex) throws IOException { //oc.write(lodCalculatorFactory, "lodCalculatorFactory", null); oc.write(lodEntropy, "lodEntropy", null); oc.write(geomap, "geomap", null); - + setMesh(temp); } @@ -918,7 +1058,7 @@ public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); size = ic.readInt("size", 16); totalSize = ic.readInt("totalSize", 16); - quadrant = ic.readShort("quadrant", (short)0); + quadrant = ic.readShort("quadrant", (short) 0); stepScale = (Vector3f) ic.readSavable("stepScale", Vector3f.UNIT_XYZ); offset = (Vector2f) ic.readSavable("offset", Vector3f.UNIT_XYZ); offsetAmount = ic.readFloat("offsetAmount", 0); @@ -927,8 +1067,8 @@ public void read(JmeImporter im) throws IOException { //lodCalculatorFactory = (LodCalculatorFactory) ic.readSavable("lodCalculatorFactory", null); lodEntropy = ic.readFloatArray("lodEntropy", null); geomap = (LODGeomap) ic.readSavable("geomap", null); - - Mesh regen = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false); + + Mesh regen = geomap.createMesh(stepScale, new Vector2f(1, 1), offset, offsetAmount, totalSize, false); setMesh(regen); //TangentBinormalGenerator.generate(this); // note that this will be removed ensurePositiveVolumeBBox(); @@ -957,9 +1097,9 @@ public TerrainPatch clone() { protected void ensurePositiveVolumeBBox() { if (getModelBound() instanceof BoundingBox) { - if (((BoundingBox)getModelBound()).getYExtent() < 0.001f) { + if (((BoundingBox) getModelBound()).getYExtent() < 0.001f) { // a correction so the box always has a volume - ((BoundingBox)getModelBound()).setYExtent(0.001f); + ((BoundingBox) getModelBound()).setYExtent(0.001f); updateWorldBound(); } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java index 8cceb85bb8..0855c1dff4 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java @@ -55,6 +55,7 @@ import com.jme3.terrain.geomipmap.picking.TerrainPickData; import com.jme3.terrain.geomipmap.picking.TerrainPicker; import com.jme3.util.TangentBinormalGenerator; + import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -103,9 +104,14 @@ * +---------> +z * (world coordinates) * + * * @author Brent Owens */ public class TerrainQuad extends Node implements Terrain { + + private Vector3f meshNormal; + private float heightmapHeight; + protected Vector2f offset; protected int totalSize; // the size of this entire terrain tree (on one side) @@ -126,7 +132,9 @@ public class TerrainQuad extends Node implements Terrain { private Vector3f lastScale = Vector3f.UNIT_XYZ; protected NeighbourFinder neighbourFinder; - + + private final int DIR_RIGHT = 0, DIR_DOWN = 1, DIR_LEFT = 2, DIR_TOP = 3; + public TerrainQuad() { super("Terrain"); } @@ -142,33 +150,33 @@ public TerrainQuad() { * A TerrainQuad of totalSize 513x513 will be 513 units wide and 513 units long. * PatchSize is just used to subdivide the terrain into tiles that can be culled. *

- * @param name the name of the scene element. This is required for - * identification and comparison purposes. - * @param patchSize size of the individual patches (geometry). Power of 2 plus 1, - * must be smaller than totalSize. (eg. 33, 65...) - * @param totalSize the size of this entire terrain (on one side). Power of 2 plus 1 - * (eg. 513, 1025, 2049...) + * + * @param name the name of the scene element. This is required for + * identification and comparison purposes. + * @param patchSize size of the individual patches (geometry). Power of 2 plus 1, + * must be smaller than totalSize. (eg. 33, 65...) + * @param totalSize the size of this entire terrain (on one side). Power of 2 plus 1 + * (eg. 513, 1025, 2049...) * @param heightMap The height map to generate the terrain from (a flat - * height map will be generated if this is null). The size of one side of the heightmap - * must match the totalSize. So a 513x513 heightmap is needed for a terrain with totalSize of 513. + * height map will be generated if this is null). The size of one side of the heightmap + * must match the totalSize. So a 513x513 heightmap is needed for a terrain with totalSize of 513. */ public TerrainQuad(String name, int patchSize, int totalSize, float[] heightMap) { this(name, patchSize, totalSize, Vector3f.UNIT_XYZ, heightMap); - - affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2); + + affectedAreaBBox = new BoundingBox(new Vector3f(0, 0, 0), size * 2, Float.MAX_VALUE, size * 2); fixNormalEdges(affectedAreaBBox); addControl(new NormalRecalcControl(this)); } - + /** - * - * @param name the name of the scene element. This is required for - * identification and comparison purposes. + * @param name the name of the scene element. This is required for + * identification and comparison purposes. * @param patchSize size of the individual patches * @param quadSize * @param totalSize the size of this entire terrain tree (on one side) * @param heightMap The height map to generate the terrain from (a flat - * height map will be generated if this is null) + * height map will be generated if this is null) */ @Deprecated public TerrainQuad(String name, int patchSize, int quadSize, int totalSize, float[] heightMap) { @@ -176,14 +184,13 @@ public TerrainQuad(String name, int patchSize, int quadSize, int totalSize, floa } /** - * - * @param name the name of the scene element. This is required for - * identification and comparison purposes. + * @param name the name of the scene element. This is required for + * identification and comparison purposes. * @param patchSize size of the individual patches - * @param size size of this quad, can be between totalSize and patchSize + * @param size size of this quad, can be between totalSize and patchSize * @param scale * @param heightMap The height map to generate the terrain from (a flat - * height map will be generated if this is null) + * height map will be generated if this is null) */ @Deprecated public TerrainQuad(String name, int patchSize, int size, Vector3f scale, float[] heightMap) { @@ -192,17 +199,16 @@ public TerrainQuad(String name, int patchSize, int size, Vector3f scale, float[] //fixNormalEdges(affectedAreaBBox); //addControl(new NormalRecalcControl(this)); } - + /** - * - * @param name the name of the scene element. This is required for - * identification and comparison purposes. + * @param name the name of the scene element. This is required for + * identification and comparison purposes. * @param patchSize size of the individual patches * @param totalSize the size of this entire terrain tree (on one side) * @param quadSize * @param scale * @param heightMap The height map to generate the terrain from (a flat - * height map will be generated if this is null) + * height map will be generated if this is null) */ @Deprecated public TerrainQuad(String name, int patchSize, int totalSize, int quadSize, Vector3f scale, float[] heightMap) { @@ -213,21 +219,20 @@ public TerrainQuad(String name, int patchSize, int totalSize, int quadSize, Vect } protected TerrainQuad(String name, int patchSize, int quadSize, - Vector3f scale, float[] heightMap, int totalSize, - Vector2f offset, float offsetAmount) - { + Vector3f scale, float[] heightMap, int totalSize, + Vector2f offset, float offsetAmount) { super(name); - + if (heightMap == null) heightMap = generateDefaultHeightMap(quadSize); - + if (!FastMath.isPowerOfTwo(quadSize - 1)) { throw new RuntimeException("size given: " + quadSize + " Terrain quad sizes may only be (2^N + 1)"); } if (FastMath.sqrt(heightMap.length) > quadSize) { Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Heightmap size is larger than the terrain size. Make sure your heightmap image is the same size as the terrain!"); } - + this.offset = offset; this.offsetAmount = offsetAmount; this.totalSize = totalSize; @@ -246,14 +251,14 @@ public void setNeighbourFinder(NeighbourFinder neighbourFinder) { * Forces the recalculation of all normals on the terrain. */ public void recalculateAllNormals() { - affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2); + affectedAreaBBox = new BoundingBox(new Vector3f(0, 0, 0), totalSize * 2, Float.MAX_VALUE, totalSize * 2); } - + /** * Create just a flat heightmap */ private float[] generateDefaultHeightMap(int size) { - float[] heightMap = new float[size*size]; + float[] heightMap = new float[size * size]; return heightMap; } @@ -267,17 +272,17 @@ protected void updateNormals() { //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 } } - + /** * Caches the transforms (except rotation) so the LOD calculator, * which runs on a separate thread, can access them safely. */ protected void cacheTerrainTransforms() { - for (int i = children.size(); --i >= 0;) { + for (int i = children.size(); --i >= 0; ) { Spatial child = children.get(i); if (child instanceof TerrainQuad) { ((TerrainQuad) child).cacheTerrainTransforms(); @@ -307,21 +312,22 @@ private int collideWithRay(Ray ray, CollisionResults results) { /** * Generate the entropy values for the terrain for the "perspective" LOD * calculator. This routine can take a long time to run! + * * @param progressMonitor optional */ public void generateEntropy(ProgressMonitor progressMonitor) { // only check this on the root quad if (isRootQuad()) if (progressMonitor != null) { - int numCalc = (totalSize-1)/(patchSize-1); // make it an even number - progressMonitor.setMonitorMax(numCalc*numCalc); + int numCalc = (totalSize - 1) / (patchSize - 1); // make it an even number + progressMonitor.setMonitorMax(numCalc * numCalc); } if (children != null) { - for (int i = children.size(); --i >= 0;) { + for (int i = children.size(); --i >= 0; ) { Spatial child = children.get(i); if (child instanceof TerrainQuad) { - ((TerrainQuad) child).generateEntropy(progressMonitor); + ((TerrainQuad) child).generateEntropy(progressMonitor); } else if (child instanceof TerrainPatch) { ((TerrainPatch) child).generateLodEntropies(); if (progressMonitor != null) @@ -337,22 +343,22 @@ public void generateEntropy(ProgressMonitor progressMonitor) { } protected boolean isRootQuad() { - return (getParent() != null && !(getParent() instanceof TerrainQuad) ); + return (getParent() != null && !(getParent() instanceof TerrainQuad)); } public Material getMaterial() { return getMaterial(null); } - + public Material getMaterial(Vector3f worldLocation) { // get the material from one of the children. They all share the same material if (children != null) { - for (int i = children.size(); --i >= 0;) { + for (int i = children.size(); --i >= 0; ) { Spatial child = children.get(i); if (child instanceof TerrainQuad) { - return ((TerrainQuad)child).getMaterial(worldLocation); + return ((TerrainQuad) child).getMaterial(worldLocation); } else if (child instanceof TerrainPatch) { - return ((TerrainPatch)child).getMaterial(); + return ((TerrainPatch) child).getMaterial(); } } } @@ -362,17 +368,27 @@ public Material getMaterial(Vector3f worldLocation) { public int getNumMajorSubdivisions() { return 1; } - - protected boolean calculateLod(List location, HashMap updates, LodCalculator lodCalculator) { + + /** + * hasLodChanged retrieves a boolean value based on the result of the calculateLod method + * in a lodCalculator subclass. + * + * @param location the Vector3f location + * @param updates HashMap with updates to the TerrainPatches + * @param lodCalculator the kind of lodCalculator + * @return + */ + protected boolean hasLodChanged(List location, HashMap updates, LodCalculator lodCalculator) { boolean lodChanged = false; if (children != null) { - for (int i = children.size(); --i >= 0;) { + + for (int i = children.size(); --i >= 0; ) { Spatial child = children.get(i); if (child instanceof TerrainQuad) { - boolean b = ((TerrainQuad) child).calculateLod(location, updates, lodCalculator); + boolean b = ((TerrainQuad) child).hasLodChanged(location, updates, lodCalculator); if (b) lodChanged = true; } else if (child instanceof TerrainPatch) { @@ -386,80 +402,54 @@ protected boolean calculateLod(List location, HashMap updated) { if (children != null) { - for (int x = children.size(); --x >= 0;) { + for (int x = children.size(); --x >= 0; ) { Spatial child = children.get(x); if (child instanceof TerrainQuad) { ((TerrainQuad) child).findNeighboursLod(updated); } else if (child instanceof TerrainPatch) { - TerrainPatch patch = (TerrainPatch) child; - if (!patch.searchedForNeighboursAlready) { - // set the references to the neighbours - patch.rightNeighbour = findRightPatch(patch); - patch.bottomNeighbour = findDownPatch(patch); - patch.leftNeighbour = findLeftPatch(patch); - patch.topNeighbour = findTopPatch(patch); - patch.searchedForNeighboursAlready = true; - } - TerrainPatch right = patch.rightNeighbour; - TerrainPatch down = patch.bottomNeighbour; - TerrainPatch left = patch.leftNeighbour; - TerrainPatch top = patch.topNeighbour; - UpdatedTerrainPatch utp = updated.get(patch.getName()); + + setPatchNeighbourReferences(patch); + TerrainPatch[] neighbours = patch.getNeighbours(); + 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); - updated.put(utpR.getName(), utpR); - utpR.setNewLod(right.lod); - } - utp.setRightLod(utpR.getNewLod()); - utpR.setLeftLod(utp.getNewLod()); - } - if (down != null) { - UpdatedTerrainPatch utpD = updated.get(down.getName()); - if (utpD == null) { - utpD = new UpdatedTerrainPatch(down); - updated.put(utpD.getName(), utpD); - utpD.setNewLod(down.lod); - } - utp.setBottomLod(utpD.getNewLod()); - utpD.setTopLod(utp.getNewLod()); - } - - if (left != null) { - UpdatedTerrainPatch utpL = updated.get(left.getName()); - if (utpL == null) { - utpL = new UpdatedTerrainPatch(left); - updated.put(utpL.getName(), utpL); - utpL.setNewLod(left.lod); - } - utp.setLeftLod(utpL.getNewLod()); - utpL.setRightLod(utp.getNewLod()); - } - if (top != null) { - UpdatedTerrainPatch utpT = updated.get(top.getName()); - if (utpT == null) { - utpT = new UpdatedTerrainPatch(top); - updated.put(utpT.getName(), utpT); - utpT.setNewLod(top.lod); + for (int direction = 0; direction < 4; direction++) { + if (neighbours[direction] != null) { + int oppositeDirection = (direction + 2) > 4 ? 4 - (direction + 2) : direction + 2; + + UpdatedTerrainPatch neighbourUtp = getUpdatedTerrainPatch(updated, neighbours[direction]); + utp.setLod(neighbourUtp.getNewLod(), direction); + neighbourUtp.setLod(utp.getNewLod(), oppositeDirection); } - utp.setTopLod(utpT.getNewLod()); - utpT.setBottomLod(utp.getNewLod()); } } } } } + private void setPatchNeighbourReferences(TerrainPatch patch) { + if (!patch.searchedForNeighboursAlready) { + // set the references to the neighbours + patch.rightNeighbour = patch.findPatch(DIR_RIGHT); + patch.bottomNeighbour = patch.findPatch(DIR_DOWN); + patch.leftNeighbour = patch.findPatch(DIR_LEFT); + patch.topNeighbour = patch.findPatch(DIR_TOP); + patch.searchedForNeighboursAlready = true; + } + } + /** * Reset the cached references of neighbours. * TerrainQuad caches neighbours for faster LOD checks. @@ -467,7 +457,7 @@ protected synchronized void findNeighboursLod(HashMap= 0;) { + for (int x = children.size(); --x >= 0; ) { Spatial child = children.get(x); if (child instanceof TerrainQuad) { ((TerrainQuad) child).resetCachedNeighbours(); @@ -478,14 +468,15 @@ public void resetCachedNeighbours() { } } } - + /** * Find any neighbours that should have their edges seamed because another neighbour * changed its LOD to a greater value (less detailed) + * @param updated TerrainPatches that have an updated LOD. */ - protected synchronized void fixEdges(HashMap updated) { + protected synchronized void fixEdges(HashMap updated) { if (children != null) { - for (int x = children.size(); --x >= 0;) { + for (int x = children.size(); --x >= 0; ) { Spatial child = children.get(x); if (child instanceof TerrainQuad) { ((TerrainQuad) child).fixEdges(updated); @@ -494,57 +485,18 @@ protected synchronized void fixEdges(HashMap updated UpdatedTerrainPatch utp = updated.get(patch.getName()); if(utp != null && utp.lodChanged()) { - if (!patch.searchedForNeighboursAlready) { - // set the references to the neighbours - patch.rightNeighbour = findRightPatch(patch); - patch.bottomNeighbour = findDownPatch(patch); - patch.leftNeighbour = findLeftPatch(patch); - patch.topNeighbour = findTopPatch(patch); - patch.searchedForNeighboursAlready = true; - } - TerrainPatch right = patch.rightNeighbour; - TerrainPatch down = patch.bottomNeighbour; - TerrainPatch top = patch.topNeighbour; - TerrainPatch left = patch.leftNeighbour; - if (right != null) { - UpdatedTerrainPatch utpR = updated.get(right.getName()); - if (utpR == null) { - utpR = new UpdatedTerrainPatch(right); - updated.put(utpR.getName(), utpR); - utpR.setNewLod(right.lod); - } - utpR.setLeftLod(utp.getNewLod()); - utpR.setFixEdges(true); - } - if (down != null) { - UpdatedTerrainPatch utpD = updated.get(down.getName()); - if (utpD == null) { - utpD = new UpdatedTerrainPatch(down); - updated.put(utpD.getName(), utpD); - utpD.setNewLod(down.lod); - } - utpD.setTopLod(utp.getNewLod()); - utpD.setFixEdges(true); - } - if (top != null){ - UpdatedTerrainPatch utpT = updated.get(top.getName()); - if (utpT == null) { - utpT = new UpdatedTerrainPatch(top); - updated.put(utpT.getName(), utpT); - utpT.setNewLod(top.lod); - } - utpT.setBottomLod(utp.getNewLod()); - utpT.setFixEdges(true); - } - if (left != null){ - UpdatedTerrainPatch utpL = updated.get(left.getName()); - if (utpL == null) { - utpL = new UpdatedTerrainPatch(left); - updated.put(utpL.getName(), utpL); - utpL.setNewLod(left.lod); + setPatchNeighbourReferences(patch); + + TerrainPatch[] neighbours = patch.getNeighbours(); + + for (int direction = 0; direction < 4; direction++) { + if (neighbours[direction] != null) { + int oppositeDirection = (direction + 2) > 4 ? 4 - (direction + 2) : direction + 2; + + UpdatedTerrainPatch neighbourUtp = getUpdatedTerrainPatch(updated, neighbours[direction]); + neighbourUtp.setLod(utp.getNewLod(), oppositeDirection); + neighbourUtp.setFixEdges(true); } - utpL.setRightLod(utp.getNewLod()); - utpL.setFixEdges(true); } } } @@ -552,9 +504,19 @@ protected synchronized void fixEdges(HashMap updated } } - protected synchronized void reIndexPages(HashMap updated, boolean usesVariableLod) { + private UpdatedTerrainPatch getUpdatedTerrainPatch(HashMap updated, TerrainPatch tp) { + UpdatedTerrainPatch utpR = updated.get(tp.getName()); + if (utpR == null) { + utpR = new UpdatedTerrainPatch(tp); + updated.put(utpR.getName(), utpR); + utpR.setNewLod(tp.lod); + } + return utpR; + } + + protected synchronized void reIndexPages(HashMap updated, boolean usesVariableLod) { if (children != null) { - for (int i = children.size(); --i >= 0;) { + for (int i = children.size(); --i >= 0; ) { Spatial child = children.get(i); if (child instanceof TerrainQuad) { ((TerrainQuad) child).reIndexPages(updated, usesVariableLod); @@ -571,14 +533,12 @@ protected synchronized void reIndexPages(HashMap upd * children. If the child's size is less than or equal to the set block * size, then patches are created, otherwise, quads are created. * - * @param blockSize - * the blocks size to test against. - * @param heightMap - * the height data. + * @param blockSize the blocks size to test against. + * @param heightMap the height data. */ protected void split(int blockSize, float[] heightMap) { if ((size >> 1) + 1 <= blockSize) { - createQuadPatch(heightMap); + setPatchChildren(heightMap); } else { createQuad(blockSize, heightMap); } @@ -587,14 +547,14 @@ protected void split(int blockSize, float[] heightMap) { /** * Quadrants, world coordinates, and heightmap coordinates (Y-up): - * - * -z - * -u | - * -v 1|3 - * -x ----+---- x - * 2|4 u - * | v - * z + *

+ * -z + * -u | + * -v 1|3 + * -x ----+---- x + * 2|4 u + * | v + * z * createQuad generates four new quads from this quad. * The heightmap's top left (0,0) coordinate is at the bottom, -x,-z * coordinate of the terrain, so it grows in the positive x.z direction. @@ -615,7 +575,7 @@ protected void createQuad(int blockSize, float[] heightMap) { float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split); Vector3f origin1 = new Vector3f(-quarterSize * stepScale.x, 0, - -quarterSize * stepScale.z); + -quarterSize * stepScale.z); tempOffset.x = offset.x; tempOffset.y = offset.y; @@ -623,18 +583,18 @@ protected void createQuad(int blockSize, float[] heightMap) { tempOffset.y += origin1.z; TerrainQuad quad1 = new TerrainQuad(getName() + "Quad1", blockSize, - split, stepScale, heightBlock1, totalSize, tempOffset, - offsetAmount); + split, stepScale, heightBlock1, totalSize, tempOffset, + offsetAmount); quad1.setLocalTranslation(origin1); quad1.quadrant = 1; this.attachChild(quad1); // 2 lower left of heightmap, lower left quad float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1, - split); + split); Vector3f origin2 = new Vector3f(-quarterSize * stepScale.x, 0, - quarterSize * stepScale.z); + quarterSize * stepScale.z); tempOffset = new Vector2f(); tempOffset.x = offset.x; @@ -643,18 +603,18 @@ protected void createQuad(int blockSize, float[] heightMap) { tempOffset.y += origin2.z; TerrainQuad quad2 = new TerrainQuad(getName() + "Quad2", blockSize, - split, stepScale, heightBlock2, totalSize, tempOffset, - offsetAmount); + split, stepScale, heightBlock2, totalSize, tempOffset, + offsetAmount); quad2.setLocalTranslation(origin2); quad2.quadrant = 2; this.attachChild(quad2); // 3 upper right of heightmap, upper right quad float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0, - split); + split); Vector3f origin3 = new Vector3f(quarterSize * stepScale.x, 0, - -quarterSize * stepScale.z); + -quarterSize * stepScale.z); tempOffset = new Vector2f(); tempOffset.x = offset.x; @@ -663,18 +623,18 @@ protected void createQuad(int blockSize, float[] heightMap) { tempOffset.y += origin3.z; TerrainQuad quad3 = new TerrainQuad(getName() + "Quad3", blockSize, - split, stepScale, heightBlock3, totalSize, tempOffset, - offsetAmount); + split, stepScale, heightBlock3, totalSize, tempOffset, + offsetAmount); quad3.setLocalTranslation(origin3); quad3.quadrant = 3; this.attachChild(quad3); - + // 4 lower right of heightmap, lower right quad float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1, - split - 1, split); + split - 1, split); Vector3f origin4 = new Vector3f(quarterSize * stepScale.x, 0, - quarterSize * stepScale.z); + quarterSize * stepScale.z); tempOffset = new Vector2f(); tempOffset.x = offset.x; @@ -683,8 +643,8 @@ protected void createQuad(int blockSize, float[] heightMap) { tempOffset.y += origin4.z; TerrainQuad quad4 = new TerrainQuad(getName() + "Quad4", blockSize, - split, stepScale, heightBlock4, totalSize, tempOffset, - offsetAmount); + split, stepScale, heightBlock4, totalSize, tempOffset, + offsetAmount); quad4.setLocalTranslation(origin4); quad4.quadrant = 4; this.attachChild(quad4); @@ -692,13 +652,13 @@ protected void createQuad(int blockSize, float[] heightMap) { } public void generateDebugTangents(Material mat) { - for (int x = children.size(); --x >= 0;) { + for (int x = children.size(); --x >= 0; ) { Spatial child = children.get(x); if (child instanceof TerrainQuad) { - ((TerrainQuad)child).generateDebugTangents(mat); + ((TerrainQuad) child).generateDebugTangents(mat); } else if (child instanceof TerrainPatch) { - Geometry debug = new Geometry( "Debug " + name, - TangentBinormalGenerator.genTbnLines( ((TerrainPatch)child).getMesh(), 0.8f)); + Geometry debug = new Geometry("Debug " + name, + TangentBinormalGenerator.genTbnLines(((TerrainPatch) child).getMesh(), 0.8f)); attachChild(debug); debug.setLocalTranslation(child.getLocalTranslation()); debug.setCullHint(CullHint.Never); @@ -707,111 +667,57 @@ public void generateDebugTangents(Material mat) { } } + + /** + * createHeightBlock creates the heightSubBlock according to the provided patch number. + * + * @param patchNumber + * @param heightMap + * @param split + * @return HeightSubBlock array + */ + protected float[] createHeightBlock(int patchNumber, float[] heightMap, int split) { + switch (patchNumber) { + case 1: + return createHeightSubBlock(heightMap, 0, 0, split); + case 2: + return createHeightSubBlock(heightMap, 0, split - 1, split); + case 3: + return createHeightSubBlock(heightMap, split - 1, 0, split); + case 4: + return createHeightSubBlock(heightMap, split - 1, split - 1, split); + default: + return null; + } + } + /** - * createQuadPatch creates four child patches from this quad. + * setPatchChildren sets four child patches to this quad. + * + * @param heightMap */ - protected void createQuadPatch(float[] heightMap) { - // create 4 terrain patches + protected void setPatchChildren(float[] heightMap) { + TerrainPatch terrainPatch = new TerrainPatch(); + int numberOfPatchChildren = 4; + int quarterSize = size >> 2; int halfSize = size >> 1; int split = (size + 1) >> 1; - //if (lodCalculator == null) - // lodCalculator = createDefaultLodCalculator(); // set a default one - offsetAmount += quarterSize; - // 1 lower left - float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split); - - Vector3f origin1 = new Vector3f(-halfSize * stepScale.x, 0, -halfSize - * stepScale.z); - - Vector2f tempOffset1 = new Vector2f(); - tempOffset1.x = offset.x; - tempOffset1.y = offset.y; - tempOffset1.x += origin1.x / 2; - tempOffset1.y += origin1.z / 2; - - TerrainPatch patch1 = new TerrainPatch(getName() + "Patch1", split, - stepScale, heightBlock1, origin1, totalSize, tempOffset1, - offsetAmount); - patch1.setQuadrant((short) 1); - this.attachChild(patch1); - patch1.setModelBound(new BoundingBox()); - patch1.updateModelBound(); - //patch1.setLodCalculator(lodCalculator); - //TangentBinormalGenerator.generate(patch1); - - // 2 upper left - float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1, - split); - - Vector3f origin2 = new Vector3f(-halfSize * stepScale.x, 0, 0); - - Vector2f tempOffset2 = new Vector2f(); - tempOffset2.x = offset.x; - tempOffset2.y = offset.y; - tempOffset2.x += origin1.x / 2; - tempOffset2.y += quarterSize * stepScale.z; - - TerrainPatch patch2 = new TerrainPatch(getName() + "Patch2", split, - stepScale, heightBlock2, origin2, totalSize, tempOffset2, - offsetAmount); - patch2.setQuadrant((short) 2); - this.attachChild(patch2); - patch2.setModelBound(new BoundingBox()); - patch2.updateModelBound(); - //patch2.setLodCalculator(lodCalculator); - //TangentBinormalGenerator.generate(patch2); - - // 3 lower right - float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0, - split); - - Vector3f origin3 = new Vector3f(0, 0, -halfSize * stepScale.z); - - Vector2f tempOffset3 = new Vector2f(); - tempOffset3.x = offset.x; - tempOffset3.y = offset.y; - tempOffset3.x += quarterSize * stepScale.x; - tempOffset3.y += origin3.z / 2; - - TerrainPatch patch3 = new TerrainPatch(getName() + "Patch3", split, - stepScale, heightBlock3, origin3, totalSize, tempOffset3, - offsetAmount); - patch3.setQuadrant((short) 3); - this.attachChild(patch3); - patch3.setModelBound(new BoundingBox()); - patch3.updateModelBound(); - //patch3.setLodCalculator(lodCalculator); - //TangentBinormalGenerator.generate(patch3); - - // 4 upper right - float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1, - split - 1, split); - - Vector3f origin4 = new Vector3f(0, 0, 0); - - Vector2f tempOffset4 = new Vector2f(); - tempOffset4.x = offset.x; - tempOffset4.y = offset.y; - tempOffset4.x += quarterSize * stepScale.x; - tempOffset4.y += quarterSize * stepScale.z; - - TerrainPatch patch4 = new TerrainPatch(getName() + "Patch4", split, - stepScale, heightBlock4, origin4, totalSize, tempOffset4, - offsetAmount); - patch4.setQuadrant((short) 4); - this.attachChild(patch4); - patch4.setModelBound(new BoundingBox()); - patch4.updateModelBound(); - //patch4.setLodCalculator(lodCalculator); - //TangentBinormalGenerator.generate(patch4); + for (int i = 1; i <= numberOfPatchChildren; i++) { + TerrainPatch patch = terrainPatch.createQuadPatch(this, i, createHeightBlock(i, heightMap, split), quarterSize, halfSize, split); + patch.setQuadrant((short) i); + this.attachChild(patch); + patch.setModelBound(new BoundingBox()); + patch.updateModelBound(); + } } + public float[] createHeightSubBlock(float[] heightMap, int x, - int y, int side) { + int y, int side) { float[] rVal = new float[side * side]; int bsize = (int) FastMath.sqrt(heightMap.length); int count = 0; @@ -839,13 +745,13 @@ public void attachBoundChildren(Node parent) { } else if (this.getChild(i) instanceof TerrainPatch) { BoundingVolume bv = getChild(i).getWorldBound(); if (bv instanceof BoundingBox) { - attachBoundingBox((BoundingBox)bv, parent); + attachBoundingBox((BoundingBox) bv, parent); } } } BoundingVolume bv = getWorldBound(); if (bv instanceof BoundingBox) { - attachBoundingBox((BoundingBox)bv, parent); + attachBoundingBox((BoundingBox) bv, parent); } } @@ -865,8 +771,8 @@ private void attachBoundingBox(BoundingBox bb, Node parent) { * Does this by looking at the affectedAreaBBox bounding box. If the bbox * exists already, then it will grow the box to fit the new changedPoint. * If the affectedAreaBBox is null, then it will create one of unit size. - * - * @param needToRecalculateNormals if null, will cause needToRecalculateNormals() to return false + *

+ * needToRecalculateNormals if null, will cause needToRecalculateNormals() to return false */ protected void setNormalRecalcNeeded(Vector2f changedPoint) { if (changedPoint == null) { // set needToRecalculateNormals() to false @@ -892,7 +798,7 @@ protected boolean needToRecalculateNormals() { } return false; } - + /** * This will cause all normals for this terrain quad to be recalculated */ @@ -912,101 +818,53 @@ public float getHeightmapHeight(Vector2f xz) { } /** - * This will just get the heightmap value at the supplied point, - * not an interpolated (actual) height value. + * getHeightmapHeight retrieves the heightmap value based on the quadrant of the supplied coordinates, + * not and interpolated (actual) height value. + * + * @param x int x-coordinate + * @param z int z-coordinate + * @return Float heightMapHeight */ protected float getHeightmapHeight(int x, int z) { - int quad = findQuadrant(x, z); - int split = (size + 1) >> 1; - if (children != null) { - for (int i = children.size(); --i >= 0;) { - Spatial spat = children.get(i); - int col = x; - int row = z; - boolean match = false; - - // get the childs quadrant - int childQuadrant = 0; - if (spat instanceof TerrainQuad) { - childQuadrant = ((TerrainQuad) spat).getQuadrant(); - } else if (spat instanceof TerrainPatch) { - childQuadrant = ((TerrainPatch) spat).getQuadrant(); - } - - if (childQuadrant == 1 && (quad & 1) != 0) { - match = true; - } else if (childQuadrant == 2 && (quad & 2) != 0) { - row = z - split + 1; - match = true; - } else if (childQuadrant == 3 && (quad & 4) != 0) { - col = x - split + 1; - match = true; - } else if (childQuadrant == 4 && (quad & 8) != 0) { - col = x - split + 1; - row = z - split + 1; - match = true; - } - - if (match) { - if (spat instanceof TerrainQuad) { - return ((TerrainQuad) spat).getHeightmapHeight(col, row); - } else if (spat instanceof TerrainPatch) { - return ((TerrainPatch) spat).getHeightmapHeight(col, row); - } - } - + QuadrantFinder qf = new QuadrantFinder(x, z); + qf.invoke(); + + if (qf.isMatch()) { + if (qf.spat instanceof TerrainQuad) { + return ((TerrainQuad) qf.spat).getHeightmapHeight(qf.col, qf.row); + } else if (qf.spat instanceof TerrainPatch) { + return ((TerrainPatch) qf.spat).getHeightmapHeight(qf.col, qf.row); } } + return Float.NaN; } + /** + * getMeshNormal retrieves the MeshNormal value based on the quadrant of the supplied coordinates. + * + * @param x int x-coordinate + * @param z int z-coordinate + * @return Vector3f meshNormal + */ protected Vector3f getMeshNormal(int x, int z) { - int quad = findQuadrant(x, z); - int split = (size + 1) >> 1; - if (children != null) { - for (int i = children.size(); --i >= 0;) { - Spatial spat = children.get(i); - int col = x; - int row = z; - boolean match = false; - - // get the childs quadrant - int childQuadrant = 0; - if (spat instanceof TerrainQuad) { - childQuadrant = ((TerrainQuad) spat).getQuadrant(); - } else if (spat instanceof TerrainPatch) { - childQuadrant = ((TerrainPatch) spat).getQuadrant(); - } - - if (childQuadrant == 1 && (quad & 1) != 0) { - match = true; - } else if (childQuadrant == 2 && (quad & 2) != 0) { - row = z - split + 1; - match = true; - } else if (childQuadrant == 3 && (quad & 4) != 0) { - col = x - split + 1; - match = true; - } else if (childQuadrant == 4 && (quad & 8) != 0) { - col = x - split + 1; - row = z - split + 1; - match = true; - } - - if (match) { - if (spat instanceof TerrainQuad) { - return ((TerrainQuad) spat).getMeshNormal(col, row); - } else if (spat instanceof TerrainPatch) { - return ((TerrainPatch) spat).getMeshNormal(col, row); - } - } - + QuadrantFinder qf = new QuadrantFinder(x, z); + qf.invoke(); + + if (qf.match) { + if (qf.spat instanceof TerrainQuad) { + return ((TerrainQuad) qf.spat).getMeshNormal(qf.col, qf.row); + } else if (qf.spat instanceof TerrainPatch) { + return ((TerrainPatch) qf.spat).getMeshNormal(qf.col, qf.row); } } + return null; } /** * is the 2d point inside the terrain? + * * @param x local coordinate * @param z local coordinate */ @@ -1024,64 +882,44 @@ private class QuadrantChild { int col; int row; Spatial child; - + QuadrantChild(int col, int row, Spatial child) { this.col = col; this.row = row; this.child = child; } } - + + /** + * findMatchingChild returns a new QuadrantChild object based on the quadrant of the supplied coordinates. + * + * @param x int x-coordinate + * @param z int z-coordinate + * @return QuadrantChild object + */ private QuadrantChild findMatchingChild(int x, int z) { - int quad = findQuadrant(x, z); - int split = (size + 1) >> 1; - if (children != null) { - for (int i = children.size(); --i >= 0;) { - Spatial spat = children.get(i); - int col = x; - int row = z; - boolean match = false; - - // get the childs quadrant - int childQuadrant = 0; - if (spat instanceof TerrainQuad) { - childQuadrant = ((TerrainQuad) spat).getQuadrant(); - } else if (spat instanceof TerrainPatch) { - childQuadrant = ((TerrainPatch) spat).getQuadrant(); - } + QuadrantFinder qf = new QuadrantFinder(x, z); + qf.invoke(); + + if (qf.isMatch()) + return new QuadrantChild(qf.col, qf.row, qf.spat); - if (childQuadrant == 1 && (quad & 1) != 0) { - match = true; - } else if (childQuadrant == 2 && (quad & 2) != 0) { - row = z - split + 1; - match = true; - } else if (childQuadrant == 3 && (quad & 4) != 0) { - col = x - split + 1; - match = true; - } else if (childQuadrant == 4 && (quad & 8) != 0) { - col = x - split + 1; - row = z - split + 1; - match = true; - } - if (match) - return new QuadrantChild(col, row, spat); - } - } return null; } - + /** * Get the interpolated height of the terrain at the specified point. + * * @param xz the location to get the height for * @return Float.NAN if the value does not exist, or the coordinates are outside of the terrain */ public float getHeight(Vector2f xz) { // offset - float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)(totalSize-1) / 2f); - float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)(totalSize-1) / 2f); - if (!isInside((int)x, (int)z)) + float x = (float) (((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float) (totalSize - 1) / 2f); + float z = (float) (((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float) (totalSize - 1) / 2f); + if (!isInside((int) x, (int) z)) return Float.NaN; - float height = getHeight((int)x, (int)z, (x%1f), (z%1f)); + float height = getHeight((int) x, (int) z, (x % 1f), (z % 1f)); height *= getWorldScale().y; return height; } @@ -1090,8 +928,8 @@ public float getHeight(Vector2f xz) { * gets an interpolated value at the specified point */ protected float getHeight(int x, int z, float xm, float zm) { - - QuadrantChild match = findMatchingChild(x,z); + + QuadrantChild match = findMatchingChild(x, z); if (match != null) { if (match.child instanceof TerrainQuad) { return ((TerrainQuad) match.child).getHeight(match.col, match.row, xm, zm); @@ -1104,20 +942,20 @@ protected float getHeight(int x, int z, float xm, float zm) { public Vector3f getNormal(Vector2f xz) { // offset - float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)(totalSize-1) / 2f); - float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)(totalSize-1) / 2f); + float x = (float) (((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float) (totalSize - 1) / 2f); + float z = (float) (((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float) (totalSize - 1) / 2f); Vector3f normal = getNormal(x, z, xz); - + return normal; } - + protected Vector3f getNormal(float x, float z, Vector2f xz) { - x-=0.5f; - z-=0.5f; + x -= 0.5f; + z -= 0.5f; float col = FastMath.floor(x); float row = FastMath.floor(z); boolean onX = false; - if(1 - (x - col)-(z - row) < 0) // what triangle to interpolate on + if (1 - (x - col) - (z - row) < 0) // what triangle to interpolate on onX = true; // v1--v2 ^ // | / | | @@ -1125,15 +963,15 @@ protected Vector3f getNormal(float x, float z, Vector2f xz) { // v3--v4 | Z // | // <-------Y - // X + // X Vector3f n1 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.ceil(z)); Vector3f n2 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.ceil(z)); Vector3f n3 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.floor(z)); Vector3f n4 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.floor(z)); - + return n1.add(n2).add(n3).add(n4).normalize(); } - + public void setHeight(Vector2f xz, float height) { List coord = new ArrayList(); coord.add(xz); @@ -1169,19 +1007,19 @@ protected void setHeight(List xz, List height, boolean override List locations = new ArrayList(); // offset - for (int i=0; i locations, boolean overrideHeight) { @@ -1212,7 +1052,7 @@ protected void setHeight(List locations, boolean overrideHeight) Spatial quad4 = null; // get the child quadrants - for (int i = children.size(); --i >= 0;) { + for (int i = children.size(); --i >= 0; ) { Spatial spat = children.get(i); int childQuadrant = 0; if (spat instanceof TerrainQuad) { @@ -1260,30 +1100,30 @@ else if (childQuadrant == 4) // send the locations to the children if (!quadLH1.isEmpty()) { if (quad1 instanceof TerrainQuad) - ((TerrainQuad)quad1).setHeight(quadLH1, overrideHeight); - else if(quad1 instanceof TerrainPatch) - ((TerrainPatch)quad1).setHeight(quadLH1, overrideHeight); + ((TerrainQuad) quad1).setHeight(quadLH1, overrideHeight); + else if (quad1 instanceof TerrainPatch) + ((TerrainPatch) quad1).setHeight(quadLH1, overrideHeight); } if (!quadLH2.isEmpty()) { if (quad2 instanceof TerrainQuad) - ((TerrainQuad)quad2).setHeight(quadLH2, overrideHeight); - else if(quad2 instanceof TerrainPatch) - ((TerrainPatch)quad2).setHeight(quadLH2, overrideHeight); + ((TerrainQuad) quad2).setHeight(quadLH2, overrideHeight); + else if (quad2 instanceof TerrainPatch) + ((TerrainPatch) quad2).setHeight(quadLH2, overrideHeight); } if (!quadLH3.isEmpty()) { if (quad3 instanceof TerrainQuad) - ((TerrainQuad)quad3).setHeight(quadLH3, overrideHeight); - else if(quad3 instanceof TerrainPatch) - ((TerrainPatch)quad3).setHeight(quadLH3, overrideHeight); + ((TerrainQuad) quad3).setHeight(quadLH3, overrideHeight); + else if (quad3 instanceof TerrainPatch) + ((TerrainPatch) quad3).setHeight(quadLH3, overrideHeight); } if (!quadLH4.isEmpty()) { if (quad4 instanceof TerrainQuad) - ((TerrainQuad)quad4).setHeight(quadLH4, overrideHeight); - else if(quad4 instanceof TerrainPatch) - ((TerrainPatch)quad4).setHeight(quadLH4, overrideHeight); + ((TerrainQuad) quad4).setHeight(quadLH4, overrideHeight); + else if (quad4 instanceof TerrainPatch) + ((TerrainPatch) quad4).setHeight(quadLH4, overrideHeight); } } @@ -1291,7 +1131,7 @@ protected boolean isPointOnTerrain(int x, int z) { return (x >= 0 && x <= totalSize && z >= 0 && z <= totalSize); } - + public int getTerrainSize() { return totalSize; } @@ -1315,6 +1155,7 @@ private int findQuadrant(int x, int y) { /** * lock or unlock the meshes of this terrain. * Locked meshes are uneditable but have better performance. + * * @param locked or unlocked */ public void setLocked(boolean locked) { @@ -1342,7 +1183,7 @@ public void setQuadrant(short quadrant) { protected TerrainPatch getPatch(int quad) { if (children != null) - for (int x = children.size(); --x >= 0;) { + for (int x = children.size(); --x >= 0; ) { Spatial child = children.get(x); if (child instanceof TerrainPatch) { TerrainPatch tb = (TerrainPatch) child; @@ -1357,7 +1198,7 @@ protected TerrainQuad getQuad(int quad) { if (quad == 0) return this; if (children != null) - for (int x = children.size(); --x >= 0;) { + for (int x = children.size(); --x >= 0; ) { Spatial child = children.get(x); if (child instanceof TerrainQuad) { TerrainQuad tq = (TerrainQuad) child; @@ -1368,6 +1209,7 @@ protected TerrainQuad getQuad(int quad) { return null; } + @Deprecated protected TerrainPatch findRightPatch(TerrainPatch tp) { if (tp.getQuadrant() == 1) return getPatch(3); @@ -1388,6 +1230,7 @@ else if (tp.getQuadrant() == 3) { return null; } + @Deprecated protected TerrainPatch findDownPatch(TerrainPatch tp) { if (tp.getQuadrant() == 1) return getPatch(2); @@ -1407,7 +1250,7 @@ else if (tp.getQuadrant() == 2) { return null; } - + @Deprecated protected TerrainPatch findTopPatch(TerrainPatch tp) { if (tp.getQuadrant() == 2) return getPatch(1); @@ -1427,6 +1270,7 @@ else if (tp.getQuadrant() == 1) { return null; } + @Deprecated protected TerrainPatch findLeftPatch(TerrainPatch tp) { if (tp.getQuadrant() == 3) return getPatch(1); @@ -1446,6 +1290,134 @@ else if (tp.getQuadrant() == 1) { return null; } + protected TerrainQuad findQuad(int direction) { + boolean useFinder = false; + if (getParent() == null || !(getParent() instanceof TerrainQuad)) { + if (neighbourFinder == null) + return null; + else + useFinder = true; + } + + if (quadrant == 0) { + if (useFinder) { + switch (direction) { + case DIR_RIGHT: + return neighbourFinder.getRightQuad(this); + case DIR_DOWN: + return neighbourFinder.getDownQuad(this); + case DIR_LEFT: + return neighbourFinder.getLeftQuad(this); + case DIR_TOP: + return neighbourFinder.getTopQuad(this); + } + } + } + + switch (direction) { + case DIR_RIGHT: + return getRightNeighbourQuad(); + case DIR_DOWN: + return getDownNeighbourQuad(); + case DIR_LEFT: + return getLeftNeighbourQuad(); + case DIR_TOP: + return getTopNeighbourQuad(); + } + + return null; + } + + private TerrainQuad getRightNeighbourQuad() { + TerrainQuad pQuad = (TerrainQuad) getParent(); + TerrainQuad neighbourQuad; + switch (quadrant) { + case 1: + return pQuad.getQuad(3); + case 2: + return pQuad.getQuad(4); + case 3: + neighbourQuad = pQuad.findQuad(DIR_RIGHT); + if (neighbourQuad != null) + return neighbourQuad.getQuad(1); + break; + case 4: + case DIR_RIGHT: + neighbourQuad = pQuad.findQuad(DIR_RIGHT); + if (neighbourQuad != null) + return neighbourQuad.getQuad(2); + break; + } + return null; + } + + private TerrainQuad getDownNeighbourQuad() { + TerrainQuad pQuad = (TerrainQuad) getParent(); + TerrainQuad neighbourQuad; + switch (quadrant) { + case 1: + return pQuad.getQuad(2); + case 2: + neighbourQuad = pQuad.findQuad(DIR_DOWN); + if (neighbourQuad != null) + return neighbourQuad.getQuad(1); + break; + case 3: + return pQuad.getQuad(4); + case 4: + neighbourQuad = pQuad.findQuad(DIR_DOWN); + if (neighbourQuad != null) + return neighbourQuad.getQuad(3); + break; + } + return null; + } + + private TerrainQuad getLeftNeighbourQuad() { + TerrainQuad pQuad = (TerrainQuad) getParent(); + TerrainQuad neighbourQuad; + switch (quadrant) { + case 1: + neighbourQuad = pQuad.findQuad(DIR_LEFT); + if (neighbourQuad != null) + return neighbourQuad.getQuad(3); + break; + case 2: + neighbourQuad = pQuad.findQuad(DIR_LEFT); + if (neighbourQuad != null) + return neighbourQuad.getQuad(4); + break; + case 3: + return pQuad.getQuad(1); + case 4: + return pQuad.getQuad(2); + } + return null; + } + + private TerrainQuad getTopNeighbourQuad() { + TerrainQuad pQuad = (TerrainQuad) getParent(); + TerrainQuad neighbourQuad; + switch (quadrant) { + case 1: + neighbourQuad = pQuad.findQuad(DIR_TOP); + if (neighbourQuad != null) + return neighbourQuad.getQuad(2); + break; + case 2: + return pQuad.getQuad(1); + case 3: + neighbourQuad = pQuad.findQuad(DIR_TOP); + if (neighbourQuad != null) + return neighbourQuad.getQuad(4); + break; + case 4: + return pQuad.getQuad(3); + } + return null; + } + + @Deprecated protected TerrainQuad findRightQuad() { boolean useFinder = false; if (getParent() == null || !(getParent() instanceof TerrainQuad)) { @@ -1482,6 +1454,7 @@ else if (quadrant == 3) { return null; } + @Deprecated protected TerrainQuad findDownQuad() { boolean useFinder = false; if (getParent() == null || !(getParent() instanceof TerrainQuad)) { @@ -1518,6 +1491,7 @@ else if (quadrant == 2) { return null; } + @Deprecated protected TerrainQuad findTopQuad() { boolean useFinder = false; if (getParent() == null || !(getParent() instanceof TerrainQuad)) { @@ -1554,6 +1528,7 @@ else if (quadrant == 1) { return null; } + @Deprecated protected TerrainQuad findLeftQuad() { boolean useFinder = false; if (getParent() == null || !(getParent() instanceof TerrainQuad)) { @@ -1600,13 +1575,13 @@ protected void fixNormals(BoundingBox affectedArea) { // go through the children and see if they collide with the affectedAreaBBox // if they do, then update their normals - for (int x = children.size(); --x >= 0;) { + for (int x = children.size(); --x >= 0; ) { Spatial child = children.get(x); if (child instanceof TerrainQuad) { - if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound()) ) + if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound())) ((TerrainQuad) child).fixNormals(affectedArea); } else if (child instanceof TerrainPatch) { - if (affectedArea != null && affectedArea.intersects(((TerrainPatch) child).getWorldBound()) ) + if (affectedArea != null && affectedArea.intersects(((TerrainPatch) child).getWorldBound())) ((TerrainPatch) child).updateNormals(); // recalculate the patch's normals } } @@ -1619,32 +1594,32 @@ protected void fixNormalEdges(BoundingBox affectedArea) { if (children == null) return; - for (int x = children.size(); --x >= 0;) { + for (int x = children.size(); --x >= 0; ) { Spatial child = children.get(x); if (child instanceof TerrainQuad) { - if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound()) ) + if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound())) ((TerrainQuad) child).fixNormalEdges(affectedArea); } else if (child instanceof TerrainPatch) { - if (affectedArea != null && !affectedArea.intersects(((TerrainPatch) child).getWorldBound()) ) // if doesn't intersect, continue + if (affectedArea != null && !affectedArea.intersects(((TerrainPatch) child).getWorldBound())) // if doesn't intersect, continue continue; TerrainPatch tp = (TerrainPatch) child; - TerrainPatch right = findRightPatch(tp); - TerrainPatch bottom = findDownPatch(tp); - TerrainPatch top = findTopPatch(tp); - TerrainPatch left = findLeftPatch(tp); + TerrainPatch right = tp.findPatch(DIR_RIGHT); + TerrainPatch bottom = tp.findPatch(DIR_DOWN); + TerrainPatch top = tp.findPatch(DIR_TOP); + TerrainPatch left = tp.findPatch(DIR_LEFT); TerrainPatch topLeft = null; if (top != null) - topLeft = findLeftPatch(top); + topLeft = top.findPatch(DIR_LEFT); TerrainPatch bottomRight = null; if (right != null) - bottomRight = findDownPatch(right); + bottomRight = right.findPatch(DIR_DOWN); TerrainPatch topRight = null; if (top != null) - topRight = findRightPatch(top); + topRight = top.findPatch(DIR_RIGHT); TerrainPatch bottomLeft = null; if (left != null) - bottomLeft = findDownPatch(left); + bottomLeft = left.findPatch(DIR_DOWN); tp.fixNormalEdges(right, bottom, top, left, bottomRight, bottomLeft, topRight, topLeft); @@ -1654,20 +1629,19 @@ protected void fixNormalEdges(BoundingBox affectedArea) { } - @Override - public int collideWith(Collidable other, CollisionResults results){ + public int collideWith(Collidable other, CollisionResults results) { int total = 0; if (other instanceof Ray) - return collideWithRay((Ray)other, results); + return collideWithRay((Ray) other, results); // if it didn't collide with this bbox, return if (other instanceof BoundingVolume) - if (!this.getWorldBound().intersects((BoundingVolume)other)) + if (!this.getWorldBound().intersects((BoundingVolume) other)) return total; - for (Spatial child : children){ + for (Spatial child : children) { total += child.collideWith(other, results); } return total; @@ -1676,6 +1650,7 @@ public int collideWith(Collidable other, CollisionResults results){ /** * Gather the terrain patches that intersect the given ray (toTest). * This only tests the bounding boxes + * * @param toTest * @param results */ @@ -1696,8 +1671,7 @@ public void findPick(Ray toTest, List results) { results.add(new TerrainPickData(tp, cr.getClosestCollision())); } } - } - else if (children.get(i) instanceof TerrainQuad) { + } else if (children.get(i) instanceof TerrainQuad) { ((TerrainQuad) children.get(i)).findPick(toTest, results); } } @@ -1709,30 +1683,31 @@ else if (children.get(i) instanceof TerrainQuad) { /** * Retrieve all Terrain Patches from all children and store them * in the 'holder' list + * * @param holder must not be null, will be populated when returns */ public void getAllTerrainPatches(List holder) { if (children != null) { - for (int i = children.size(); --i >= 0;) { + for (int i = children.size(); --i >= 0; ) { Spatial child = children.get(i); if (child instanceof TerrainQuad) { ((TerrainQuad) child).getAllTerrainPatches(holder); } else if (child instanceof TerrainPatch) { - holder.add((TerrainPatch)child); + holder.add((TerrainPatch) child); } } } } - public void getAllTerrainPatchesWithTranslation(Map holder, Vector3f translation) { + public void getAllTerrainPatchesWithTranslation(Map holder, Vector3f translation) { if (children != null) { - for (int i = children.size(); --i >= 0;) { + for (int i = children.size(); --i >= 0; ) { Spatial child = children.get(i); if (child instanceof TerrainQuad) { ((TerrainQuad) child).getAllTerrainPatchesWithTranslation(holder, translation.clone().add(child.getLocalTranslation())); } else if (child instanceof TerrainPatch) { //if (holder.size() < 4) - holder.put((TerrainPatch)child, translation.clone().add(child.getLocalTranslation())); + holder.put((TerrainPatch) child, translation.clone().add(child.getLocalTranslation())); } } } @@ -1744,14 +1719,14 @@ public void read(JmeImporter e) throws IOException { InputCapsule c = e.getCapsule(this); size = c.readInt("size", 0); stepScale = (Vector3f) c.readSavable("stepScale", null); - offset = (Vector2f) c.readSavable("offset", new Vector2f(0,0)); + offset = (Vector2f) c.readSavable("offset", new Vector2f(0, 0)); offsetAmount = c.readFloat("offsetAmount", 0); quadrant = c.readInt("quadrant", 0); totalSize = c.readInt("totalSize", 0); //lodCalculator = (LodCalculator) c.readSavable("lodCalculator", createDefaultLodCalculator()); //lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null); - - if ( !(getParent() instanceof TerrainQuad) ) { + + if (!(getParent() instanceof TerrainQuad)) { BoundingBox all = new BoundingBox(getWorldTranslation(), totalSize, totalSize, totalSize); affectedAreaBBox = all; updateNormals(); @@ -1765,7 +1740,7 @@ public void write(JmeExporter e) throws IOException { c.write(size, "size", 0); c.write(totalSize, "totalSize", 0); c.write(stepScale, "stepScale", null); - c.write(offset, "offset", new Vector2f(0,0)); + c.write(offset, "offset", new Vector2f(0, 0)); c.write(offsetAmount, "offsetAmount", 0); c.write(quadrant, "quadrant", 0); //c.write(lodCalculatorFactory, "lodCalculatorFactory", null); @@ -1777,7 +1752,7 @@ public TerrainQuad clone() { return this.clone(true); } - @Override + @Override public TerrainQuad clone(boolean cloneMaterials) { TerrainQuad quadClone = (TerrainQuad) super.clone(cloneMaterials); quadClone.name = name.toString(); @@ -1793,10 +1768,10 @@ public TerrainQuad clone(boolean cloneMaterials) { quadClone.quadrant = quadrant; //quadClone.lodCalculatorFactory = lodCalculatorFactory.clone(); //quadClone.lodCalculator = lodCalculator.clone(); - + TerrainLodControl lodControlCloned = this.getControl(TerrainLodControl.class); TerrainLodControl lodControl = quadClone.getControl(TerrainLodControl.class); - + if (lodControlCloned != null && !(getParent() instanceof TerrainQuad)) { //lodControlCloned.setLodCalculator(lodControl.getLodCalculator().clone()); } @@ -1806,7 +1781,7 @@ public TerrainQuad clone(boolean cloneMaterials) { return quadClone; } - + @Override protected void setParent(Node parent) { super.setParent(parent); @@ -1815,7 +1790,7 @@ protected void setParent(Node parent) { clearCaches(); } } - + /** * Removes any cached references this terrain is holding, in particular * the TerrainPatch's neighbour references. @@ -1824,7 +1799,7 @@ protected void setParent(Node parent) { */ public void clearCaches() { if (children != null) { - for (int i = children.size(); --i >= 0;) { + for (int i = children.size(); --i >= 0; ) { Spatial child = children.get(i); if (child instanceof TerrainQuad) { ((TerrainQuad) child).clearCaches(); @@ -1834,10 +1809,10 @@ public void clearCaches() { } } } - + public int getMaxLod() { if (maxLod < 0) - maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide + maxLod = Math.max(1, (int) (FastMath.log(size - 1) / FastMath.log(2)) - 1); // -1 forces our minimum of 4 triangles wide return maxLod; } @@ -1850,29 +1825,37 @@ public int getTotalSize() { return totalSize; } + public float getOffsetAmount() { + return offsetAmount; + } + + public void setOffsetAmount(float offsetAmount) { + this.offsetAmount = offsetAmount; + } + + public float[] getHeightMap() { float[] hm = null; - int length = ((size-1)/2)+1; - int area = size*size; + int length = ((size - 1) / 2) + 1; + int area = size * size; hm = new float[area]; if (getChildren() != null && !getChildren().isEmpty()) { - float[] ul=null, ur=null, bl=null, br=null; + float[] ul = null, ur = null, bl = null, br = null; // get the child heightmaps if (getChild(0) instanceof TerrainPatch) { for (Spatial s : getChildren()) { - if ( ((TerrainPatch)s).getQuadrant() == 1) - ul = ((TerrainPatch)s).getHeightMap(); - else if(((TerrainPatch) s).getQuadrant() == 2) - bl = ((TerrainPatch)s).getHeightMap(); - else if(((TerrainPatch) s).getQuadrant() == 3) - ur = ((TerrainPatch)s).getHeightMap(); - else if(((TerrainPatch) s).getQuadrant() == 4) - br = ((TerrainPatch)s).getHeightMap(); + if (((TerrainPatch) s).getQuadrant() == 1) + ul = ((TerrainPatch) s).getHeightMap(); + else if (((TerrainPatch) s).getQuadrant() == 2) + bl = ((TerrainPatch) s).getHeightMap(); + else if (((TerrainPatch) s).getQuadrant() == 3) + ur = ((TerrainPatch) s).getHeightMap(); + else if (((TerrainPatch) s).getQuadrant() == 4) + br = ((TerrainPatch) s).getHeightMap(); } - } - else { + } else { ul = getQuad(1).getHeightMap(); bl = getQuad(2).getHeightMap(); ur = getQuad(3).getHeightMap(); @@ -1883,31 +1866,88 @@ else if(((TerrainPatch) s).getQuadrant() == 4) // first upper blocks - for (int y=0; y> 1; + + if (children != null) { + for (int i = children.size(); --i >= 0; ) { + this.spat = children.get(i); + + // get the childs quadrant + int childQuadrant = 0; + if (this.spat instanceof TerrainQuad) { + childQuadrant = ((TerrainQuad) this.spat).getQuadrant(); + } else if (this.spat instanceof TerrainPatch) { + childQuadrant = ((TerrainPatch) this.spat).getQuadrant(); + } + + if (childQuadrant == 1 && (quad & 1) != 0) { + this.match = true; + } else if (childQuadrant == 2 && (quad & 2) != 0) { + this.row = this.z - split + 1; + this.match = true; + } else if (childQuadrant == 3 && (quad & 4) != 0) { + this.col = this.x - split + 1; + this.match = true; + } else if (childQuadrant == 4 && (quad & 8) != 0) { + this.col = this.x - split + 1; + this.row = this.z - split + 1; + this.match = true; + } + + if (this.match) { + return true; + } + } + } + + return false; + } + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java index f6d370b238..594e500505 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java @@ -53,6 +53,8 @@ public class UpdatedTerrainPatch { //private boolean reIndexNeeded = false; private boolean fixEdges = false; + private final int DIR_RIGHT = 0, DIR_DOWN = 1, DIR_LEFT = 2, DIR_TOP = 3; + public UpdatedTerrainPatch(TerrainPatch updatedPatch) { this.updatedPatch = updatedPatch; } @@ -99,12 +101,21 @@ protected void setNewIndexBuffer(Buffer newIndexBuffer) { this.newIndexBuffer = newIndexBuffer; } + protected void setLod(int lod, int dir) { + switch (dir) { + case DIR_LEFT: this.leftLod = lod; break; + case DIR_TOP: this.topLod = lod; break; + case DIR_RIGHT: this.rightLod = lod; break; + case DIR_DOWN: this.bottomLod = lod; break; + } + } protected int getRightLod() { return rightLod; } + @Deprecated protected void setRightLod(int rightLod) { this.rightLod = rightLod; } @@ -115,6 +126,7 @@ protected int getTopLod() { } + @Deprecated protected void setTopLod(int topLod) { this.topLod = topLod; } @@ -125,6 +137,7 @@ protected int getLeftLod() { } + @Deprecated protected void setLeftLod(int leftLod) { this.leftLod = leftLod; } @@ -135,6 +148,7 @@ protected int getBottomLod() { } + @Deprecated protected void setBottomLod(int bottomLod) { this.bottomLod = bottomLod; } diff --git a/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/FakeDistanceLodCalculator.java b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/FakeDistanceLodCalculator.java new file mode 100644 index 0000000000..89dd0999c3 --- /dev/null +++ b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/FakeDistanceLodCalculator.java @@ -0,0 +1,15 @@ +package com.jme3.terrain.geomipmap; + +import com.jme3.math.Vector3f; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; + + +import java.util.HashMap; +import java.util.List; + +public class FakeDistanceLodCalculator extends DistanceLodCalculator { + + @Override + public boolean calculateLod(TerrainPatch terrainPatch, List locations, HashMap updates){return true;} + +} diff --git a/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/FakeTerrainPatch.java b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/FakeTerrainPatch.java new file mode 100644 index 0000000000..52de1995f8 --- /dev/null +++ b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/FakeTerrainPatch.java @@ -0,0 +1,8 @@ +package com.jme3.terrain.geomipmap; + +import com.jme3.math.Vector3f; + +public class FakeTerrainPatch extends TerrainPatch { + + public Vector3f worldTranslationCached = super.worldTranslationCached; +} diff --git a/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/FakeTerrainQuad.java b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/FakeTerrainQuad.java new file mode 100644 index 0000000000..429209d339 --- /dev/null +++ b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/FakeTerrainQuad.java @@ -0,0 +1,8 @@ +package com.jme3.terrain.geomipmap; + +public class FakeTerrainQuad extends TerrainQuad { + + public TerrainQuad getQuad(int quad) { + return super.getQuad(quad); + } +} diff --git a/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/TerrainPatchTest.java b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/TerrainPatchTest.java new file mode 100644 index 0000000000..28f8058757 --- /dev/null +++ b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/TerrainPatchTest.java @@ -0,0 +1,86 @@ +package com.jme3.terrain.geomipmap; + +import com.jme3.scene.Spatial; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class TerrainPatchTest { + + final int DIR_RIGHT = 0, DIR_DOWN = 1, DIR_LEFT = 2, DIR_TOP = 3; + + /** + * Used to recursively create a nested structure of {@link Spatial}s. + * If nesting level is > 1, root element will be a {@link TerrainQuad}. + * Leafs (nesting level 0) are {@link TerrainPatch}es. + * + * @param nestLevel Nest level to be created. + * @return Nested structure of {@link Spatial}s + */ + private Spatial createNestedQuad(int nestLevel) { + if (nestLevel == 0) { + return new TerrainPatch(); + } + + FakeTerrainQuad parent = new FakeTerrainQuad(); + for (int i = 0; i < 4; i++) { + Spatial child = createNestedQuad(nestLevel - 1); + + if (child instanceof TerrainPatch) { + TerrainPatch patchChild = (TerrainPatch) child; + patchChild.quadrant = (short) (i + 1); + parent.attachChild(patchChild); + } else if (child instanceof TerrainQuad) { + FakeTerrainQuad quadChild = (FakeTerrainQuad) child; + quadChild.quadrant = i + 1; + parent.attachChild(quadChild); + } + } + + return parent; + } + + @Test + public void testFindPatch() { + // Test for nestlevel 2 + FakeTerrainQuad root = (FakeTerrainQuad)createNestedQuad(2); + + // Test for all quad children + for (int i = 1; i <= 4; i++) { + FakeTerrainQuad quadChild = (FakeTerrainQuad) root.getQuad(i); + + // Test for all patch children + for (int j = 1; j <= 4; j++) { + TerrainPatch patchChild = quadChild.getPatch(j); + + assertNotNull(patchChild); + + assertEquals(patchChild.findPatch(DIR_RIGHT), quadChild.findRightPatch(patchChild)); + assertEquals(patchChild.findPatch(DIR_DOWN),quadChild.findDownPatch(patchChild)); + assertEquals(patchChild.findPatch(DIR_LEFT),quadChild.findLeftPatch(patchChild)); + assertEquals(patchChild.findPatch(DIR_TOP), quadChild.findTopPatch(patchChild)); + + // Test nonsense direction + assertNull(patchChild.findPatch(-1)); + } + } + + // Test for nestlevel 1 + root = (FakeTerrainQuad)createNestedQuad(1); + + // Test for all patch children + for (int j = 1; j <= 4; j++) { + TerrainPatch patchChild = root.getPatch(j); + + assertNotNull(patchChild); + + assertEquals(patchChild.findPatch(DIR_RIGHT), root.findRightPatch(patchChild)); + assertEquals(patchChild.findPatch(DIR_DOWN),root.findDownPatch(patchChild)); + assertEquals(patchChild.findPatch(DIR_LEFT),root.findLeftPatch(patchChild)); + assertEquals(patchChild.findPatch(DIR_TOP), root.findTopPatch(patchChild)); + + // Test nonsense direction + assertNull(patchChild.findPatch(-1)); + } + } +} diff --git a/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/TerrainQuadTest.java b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/TerrainQuadTest.java new file mode 100644 index 0000000000..26e90c4880 --- /dev/null +++ b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/TerrainQuadTest.java @@ -0,0 +1,763 @@ +package com.jme3.terrain.geomipmap; + +import com.jme3.asset.AssetManager; +import com.jme3.bounding.BoundingBox; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.HillHeightMap; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.*; + +import static org.junit.Assert.*; + +public class TerrainQuadTest { + + final int DIR_RIGHT = 0, DIR_DOWN = 1, DIR_LEFT = 2, DIR_TOP = 3; + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + private AssetManager assetManager; + private FakeTerrainQuad parentTerrainQuad; + private TerrainQuad terrainQuad = new TerrainQuad(); + private FakeTerrainQuad[] children = new FakeTerrainQuad[4]; + private TerrainPatch[] tpChildren = new TerrainPatch[4]; + private LodCalculator lodCalculator = new DistanceLodCalculator(); + private LodCalculator fakeLodCalculator = new FakeDistanceLodCalculator(); + private List location = new ArrayList(); + private HashMap updates = new HashMap(); + private Vector3f v3f = new Vector3f(); + private Vector2f v2f = new Vector2f(); + private BoundingBox boundingBox = new BoundingBox(); + private float[] testHeightmap; + + + @Before + public void init() { + for (int i = 0; i < 4; i++) { + children[i] = new FakeTerrainQuad(); + tpChildren[i] = new TerrainPatch(); + } + + // HEIGHTMAP image (for the terrain heightmap) + //Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); + // CREATE HEIGHTMAP + AbstractHeightMap heightmap = null; + try { + heightmap = new HillHeightMap(5, 1000, 50, 100, (byte) 3); + + //heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f); + heightmap.load(); + + } catch (Exception e) { + e.printStackTrace(); + } + + + terrainQuad = new TerrainQuad("terrain_1", 3, 5, heightmap.getHeightMap()); + testHeightmap = heightmap.getHeightMap(); + + parentTerrainQuad = new FakeTerrainQuad(); + fakeCreateQuad(parentTerrainQuad, children); + } + + private void fakeCreateQuad(FakeTerrainQuad parent, FakeTerrainQuad[] children) { + for (int i = 0; i < children.length; i++) { + children[i].quadrant = i + 1; // Quadrant starts counting from 1 + parent.attachChild(children[i]); + } + } + + /** + * Used to recursively create a nested structure of {@link Spatial}s. + * If nesting level is > 1, root element will be a {@link TerrainQuad}. + * Leafs (nesting level 0) are {@link TerrainPatch}es. + * + * @param nestLevel Nest level to be created. + * @return Nested structure of {@link Spatial}s + */ + private Spatial createNestedQuad(int nestLevel, String index) { + if (nestLevel == 0) { + TerrainPatch tp = new TerrainPatch(); + tp.setName(index); + return tp; + } + + FakeTerrainQuad parent = new FakeTerrainQuad(); + parent.setName(index); + for (int i = 0; i < 4; i++) { + Spatial child = createNestedQuad(nestLevel - 1, index + (i + 1)); + + if (child instanceof TerrainPatch) { + TerrainPatch patchChild = (TerrainPatch) child; + patchChild.quadrant = (short) (i + 1); + parent.attachChild(patchChild); + } else if (child instanceof TerrainQuad) { + FakeTerrainQuad quadChild = (FakeTerrainQuad) child; + quadChild.quadrant = i + 1; + parent.attachChild(quadChild); + } + } + + return parent; + } + + @Test + public void testFakeTerrainQuad() { + FakeTerrainQuad fake = new FakeTerrainQuad(); + assertEquals(fake, fake.getQuad(0)); + } + + @Test + public void testNestStructure() { + Spatial leaf = createNestedQuad(0, ""); + assertTrue(leaf instanceof TerrainPatch); + + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(1, ""); + assertEquals(root.getChildren().size(), 4); + for (int i = 0; i < 4; i++) { + assertTrue(root.getChild(i) instanceof TerrainPatch); // Ensure children of root are leafs + } + + root = (FakeTerrainQuad) createNestedQuad(2, ""); + assertEquals(root.getChildren().size(), 4); + for (int i = 0; i < 4; i++) { + assertTrue(root.getChild(i) instanceof TerrainQuad); // Ensure children of root are not leafs + } + } + + @Test + public void testGetQuad() { + assertEquals(parentTerrainQuad.getQuad(0), parentTerrainQuad); + assertEquals(parentTerrainQuad.getQuad(1), children[0]); + assertEquals(parentTerrainQuad.getQuad(2), children[1]); + assertEquals(parentTerrainQuad.getQuad(3), children[2]); + assertEquals(parentTerrainQuad.getQuad(4), children[3]); + assertEquals(parentTerrainQuad.getQuad(5), null); + } + + @Test + public void testFindQuadNeighbourFinder() { + FakeTerrainQuad[] roots = new FakeTerrainQuad[4]; + roots[0] = (FakeTerrainQuad) createNestedQuad(2, ""); + roots[1] = (FakeTerrainQuad) createNestedQuad(2, ""); + roots[2] = (FakeTerrainQuad) createNestedQuad(2, ""); + roots[3] = (FakeTerrainQuad) createNestedQuad(2, ""); + + NeighbourFinder nf = new TestNeighbourFinder(roots[0], roots[1], roots[2], roots[3]); + for (FakeTerrainQuad root : roots) { + root.setNeighbourFinder(nf); + // Legacy code + assertEquals(root.findRightQuad(), nf.getRightQuad(root)); + assertEquals(root.findDownQuad(), nf.getDownQuad(root)); + assertEquals(root.findLeftQuad(), nf.getLeftQuad(root)); + assertEquals(root.findTopQuad(), nf.getTopQuad(root)); + + // Refactored code + assertEquals(root.findQuad(DIR_RIGHT), nf.getRightQuad(root)); + assertEquals(root.findQuad(DIR_DOWN), nf.getDownQuad(root)); + assertEquals(root.findQuad(DIR_LEFT), nf.getLeftQuad(root)); + assertEquals(root.findQuad(DIR_TOP), nf.getTopQuad(root)); + } + } + + @Test + public void testFindRightQuad() { + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(3, ""); + FakeTerrainQuad topLeftChild = (FakeTerrainQuad) root.getQuad(1); + FakeTerrainQuad topRight = (FakeTerrainQuad) root.getQuad(3); + + assertEquals(root.findRightQuad(), null); + assertEquals(topLeftChild.findRightQuad(), topRight); // Confirm position of two parent quads + + // Check quad children of parent + assertEquals(topLeftChild.getQuad(1).findRightQuad(), topLeftChild.getQuad(3)); + assertEquals(topLeftChild.getQuad(2).findRightQuad(), topLeftChild.getQuad(4)); + assertEquals(topLeftChild.getQuad(3).findRightQuad(), topRight.getQuad(1)); + assertEquals(topLeftChild.getQuad(4).findRightQuad(), topRight.getQuad(2)); + + // Check non-existing neighbour quads + assertEquals(topRight.getQuad(3).findRightQuad(), null); + assertEquals(topRight.getQuad(4).findRightQuad(), null); + } + + @Test + public void testFindDownQuad() { + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(3, ""); + FakeTerrainQuad topLeftChild = (FakeTerrainQuad) root.getQuad(1); + FakeTerrainQuad downLeftChild = (FakeTerrainQuad) root.getQuad(2); + + assertEquals(root.findDownQuad(), null); + assertEquals(topLeftChild.findDownQuad(), downLeftChild); // Confirm position of two parent quads + + // Check quad children of parent + assertEquals(topLeftChild.getQuad(1).findDownQuad(), topLeftChild.getQuad(2)); + assertEquals(topLeftChild.getQuad(2).findDownQuad(), downLeftChild.getQuad(1)); + assertEquals(topLeftChild.getQuad(3).findDownQuad(), topLeftChild.getQuad(4)); + assertEquals(topLeftChild.getQuad(4).findDownQuad(), downLeftChild.getQuad(3)); + + // Check non-existing neighbour quads + assertEquals(downLeftChild.getQuad(2).findDownQuad(), null); + assertEquals(downLeftChild.getQuad(4).findDownQuad(), null); + } + + @Test + public void testFindLeftQuad() { + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(3, ""); + FakeTerrainQuad topLeftChild = (FakeTerrainQuad) root.getQuad(1); + FakeTerrainQuad topRightChild = (FakeTerrainQuad) root.getQuad(3); + + assertEquals(root.findLeftQuad(), null); + assertEquals(topRightChild.findLeftQuad(), topLeftChild); // Confirm position of two parent quads + + // Check quad children of parent + assertEquals(topRightChild.getQuad(1).findLeftQuad(), topLeftChild.getQuad(3)); + assertEquals(topRightChild.getQuad(2).findLeftQuad(), topLeftChild.getQuad(4)); + assertEquals(topRightChild.getQuad(3).findLeftQuad(), topRightChild.getQuad(1)); + assertEquals(topRightChild.getQuad(4).findLeftQuad(), topRightChild.getQuad(2)); + + // Check non-existing neighbour quads + assertEquals(topLeftChild.getQuad(1).findLeftQuad(), null); + assertEquals(topLeftChild.getQuad(2).findLeftQuad(), null); + } + + @Test + public void testFindTopQuad() { + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(3, ""); + FakeTerrainQuad topLeftChild = (FakeTerrainQuad) root.getQuad(1); + FakeTerrainQuad downLeftChild = (FakeTerrainQuad) root.getQuad(2); + + assertEquals(root.findTopQuad(), null); + assertEquals(downLeftChild.findTopQuad(), topLeftChild); // Confirm position of two parent quads + + // Check quad children of parent + assertEquals(downLeftChild.getQuad(1).findTopQuad(), topLeftChild.getQuad(2)); + assertEquals(downLeftChild.getQuad(2).findTopQuad(), downLeftChild.getQuad(1)); + assertEquals(downLeftChild.getQuad(3).findTopQuad(), topLeftChild.getQuad(4)); + assertEquals(downLeftChild.getQuad(4).findTopQuad(), downLeftChild.getQuad(3)); + + // Check non-existing neighbour quads + assertEquals(topLeftChild.getQuad(1).findTopQuad(), null); + assertEquals(topLeftChild.getQuad(3).findTopQuad(), null); + } + + @Test + public void testGetPatch() { + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(1, ""); + assertNull(root.getPatch(0)); + for (int i = 1; i <= 4; i++) { + TerrainPatch child = root.getPatch(i); + assertNotNull(child); + assertEquals(root.getChild(i - 1), child); + } + assertEquals(root.getPatch(5), null); + } + + @Test + public void testFindRightPatch() { + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(2, ""); + FakeTerrainQuad topLeftChild = (FakeTerrainQuad) root.getQuad(1); + FakeTerrainQuad topRightChild = (FakeTerrainQuad) root.getQuad(3); + + try { + root.findRightPatch(null); + } catch (RuntimeException e) { + assertEquals(e.getClass(), NullPointerException.class); + } + + assertEquals(topLeftChild.findQuad(DIR_RIGHT), topRightChild); // Confirm position of two parent quads + + // Check quad children of parent + TerrainPatch child1 = topLeftChild.findRightPatch(topLeftChild.getPatch(1)); + assertNotNull(child1); + assertEquals(child1, topLeftChild.getPatch(3)); + + TerrainPatch child2 = topLeftChild.findRightPatch(topLeftChild.getPatch(2)); + assertNotNull(child1); + assertEquals(child2, topLeftChild.getPatch(4)); + + + TerrainPatch child3 = topLeftChild.findRightPatch(topLeftChild.getPatch(3)); + assertNotNull(child3); + assertEquals(child3, topRightChild.getPatch(1)); + + + TerrainPatch child4 = topLeftChild.findRightPatch(topLeftChild.getPatch(4)); + assertNotNull(child4); + assertEquals(child4, topRightChild.getPatch(2)); + + // Check non-existing neighbour quads + assertEquals(topRightChild.findRightPatch(topRightChild.getPatch(3)), null); + assertEquals(topRightChild.findRightPatch(topRightChild.getPatch(4)), null); + } + + @Test + public void testFindDownPatch() { + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(2, ""); + FakeTerrainQuad topLeftChild = (FakeTerrainQuad) root.getQuad(1); + FakeTerrainQuad bottomLeftChild = (FakeTerrainQuad) root.getQuad(2); + + try { + root.findDownPatch(null); + } catch (RuntimeException e) { + assertEquals(e.getClass(), NullPointerException.class); + } + + assertEquals(topLeftChild.findQuad(DIR_DOWN), bottomLeftChild); // Confirm position of two parent quads + + // Check quad children of parent + TerrainPatch child1 = topLeftChild.findDownPatch(topLeftChild.getPatch(1)); + assertNotNull(child1); + assertEquals(child1, topLeftChild.getPatch(2)); + + TerrainPatch child2 = topLeftChild.findDownPatch(topLeftChild.getPatch(2)); + assertNotNull(child1); + assertEquals(child2, bottomLeftChild.getPatch(1)); + + + TerrainPatch child3 = topLeftChild.findDownPatch(topLeftChild.getPatch(3)); + assertNotNull(child3); + assertEquals(child3, topLeftChild.getPatch(4)); + + + TerrainPatch child4 = topLeftChild.findDownPatch(topLeftChild.getPatch(4)); + assertNotNull(child4); + assertEquals(child4, bottomLeftChild.getPatch(3)); + + // Check non-existing neighbour quads + assertEquals(bottomLeftChild.findDownPatch(bottomLeftChild.getPatch(2)), null); + assertEquals(bottomLeftChild.findDownPatch(bottomLeftChild.getPatch(4)), null); + } + + @Test + public void testFindLeftPatch() { + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(2, ""); + FakeTerrainQuad topLeftChild = (FakeTerrainQuad) root.getQuad(1); + FakeTerrainQuad topRightChild = (FakeTerrainQuad) root.getQuad(3); + + try { + root.findLeftPatch(null); + } catch (RuntimeException e) { + assertEquals(e.getClass(), NullPointerException.class); + } + + assertEquals(topRightChild.findQuad(DIR_LEFT), topLeftChild); // Confirm position of two parent quads + + // Check quad children of parent + TerrainPatch child1 = topRightChild.findLeftPatch(topRightChild.getPatch(1)); + assertNotNull(child1); + assertEquals(child1, topLeftChild.getPatch(3)); + + TerrainPatch child2 = topRightChild.findLeftPatch(topRightChild.getPatch(2)); + assertNotNull(child1); + assertEquals(child2, topLeftChild.getPatch(4)); + + + TerrainPatch child3 = topRightChild.findLeftPatch(topRightChild.getPatch(3)); + assertNotNull(child3); + assertEquals(child3, topRightChild.getPatch(1)); + + + TerrainPatch child4 = topRightChild.findLeftPatch(topRightChild.getPatch(4)); + assertNotNull(child4); + assertEquals(child4, topRightChild.getPatch(2)); + + // Check non-existing neighbour quads + assertEquals(topLeftChild.findLeftPatch(topLeftChild.getPatch(1)), null); + assertEquals(topLeftChild.findLeftPatch(topLeftChild.getPatch(2)), null); + } + + @Test + public void testFindTopPatch() { + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(2, ""); + FakeTerrainQuad topRightChild = (FakeTerrainQuad) root.getQuad(3); + FakeTerrainQuad bottomRightChild = (FakeTerrainQuad) root.getQuad(4); + + try { + root.findTopPatch(null); + } catch (RuntimeException e) { + assertEquals(e.getClass(), NullPointerException.class); + } + + assertEquals(bottomRightChild.findQuad(DIR_TOP), topRightChild); // Confirm position of two parent quads + + // Check quad children of parent + TerrainPatch child1 = bottomRightChild.findTopPatch(bottomRightChild.getPatch(1)); + assertNotNull(child1); + assertEquals(child1, topRightChild.getPatch(2)); + + TerrainPatch child2 = bottomRightChild.findTopPatch(bottomRightChild.getPatch(2)); + assertNotNull(child1); + assertEquals(child2, bottomRightChild.getPatch(1)); + + + TerrainPatch child3 = bottomRightChild.findTopPatch(bottomRightChild.getPatch(3)); + assertNotNull(child3); + assertEquals(child3, topRightChild.getPatch(4)); + + + TerrainPatch child4 = bottomRightChild.findTopPatch(bottomRightChild.getPatch(4)); + assertNotNull(child4); + assertEquals(child4, bottomRightChild.getPatch(3)); + + // Check non-existing neighbour quads + assertEquals(topRightChild.findTopPatch(topRightChild.getPatch(1)), null); + assertEquals(topRightChild.findTopPatch(topRightChild.getPatch(3)), null); + } + + @Test + public void testFindQuad() { + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(2, ""); + + assertEquals(root.quadrant, 0); + + assertNull(root.findQuad(-1)); + assertNull(root.getQuad(1).findQuad(-1)); + + assertEquals(root.findQuad(DIR_RIGHT), root.findRightQuad()); + assertEquals(root.findQuad(DIR_DOWN), root.findDownQuad()); + assertEquals(root.findQuad(DIR_LEFT), root.findLeftQuad()); + assertEquals(root.findQuad(DIR_TOP), root.findTopQuad()); + + for (int i = 0; i < root.getChildren().size(); i++) { + FakeTerrainQuad child = (FakeTerrainQuad) root.getQuad(i); + assertEquals(child.findQuad(DIR_RIGHT), child.findRightQuad()); + assertEquals(child.findQuad(DIR_DOWN), child.findDownQuad()); + assertEquals(child.findQuad(DIR_LEFT), child.findLeftQuad()); + assertEquals(child.findQuad(DIR_TOP), child.findTopQuad()); + } + } + + /** + * Tests the calculateLod method, which name has been refactored to hasLodChanged. + * We came to the conclusion that the method does belong to TerrainQuad, but should be renamed + * as it does not calculate anything. Is only retrieves values from subclasses of the LodCalculator interface. + * The actual lodCalculator is defined in the calculateLod method of these childs. + */ + @Test + public void testHasLodChanged() { + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(1, ""); + assertFalse(root.hasLodChanged(location, updates, lodCalculator)); + assertTrue(root.hasLodChanged(location, updates, fakeLodCalculator)); + + FakeTerrainQuad leaf = (FakeTerrainQuad) createNestedQuad(1, ""); + leaf.attachChild(children[1]); + assertTrue(leaf.hasLodChanged(location, updates, fakeLodCalculator)); + } + + + /** + * Tests the refactored createQuadPatch method, which name is refactored to setPatchChildren. + * setPatchChildren makes use of two new methods createHeightBlock and createQuadPatch (part of TerrainPatch). + * This is the first of 4 tests, as setPatchChildren couples 4 TerrainPatches to + * a TerrainQuad. Each tests makes sure that the correct TerrainPatch child has been coupled. + */ + @Test + public void testSetPatchChildren1() { + String patch1 = "terrain_1Patch1"; + + boundingBox.setCenter(1.0f, 54.88082f, 1.0f); + terrainQuad.setPatchChildren(testHeightmap); + + assertTrue(terrainQuad.getChild(patch1) instanceof TerrainPatch); + TerrainPatch p1 = (TerrainPatch) terrainQuad.getChild(patch1); + + assertEquals(patch1, p1.getName()); + assertEquals(v2f.set(-1.0f, -1.0f), p1.getOffset()); + assertEquals(1.0f, p1.getOffsetAmount(), 0.0f); + assertEquals(v3f.add(-2.0f, 0.0f, -2.0f), p1.getLocalTranslation()); + assertEquals(9, p1.getHeightMap().length); + assertEquals(5, p1.getTotalSize()); + assertEquals(1, p1.getQuadrant()); + assertEquals(boundingBox.getCenter(), p1.getModelBound().getCenter()); + } + + @Test + public void testSetPatchChildren2() { + String patch2 = "terrain_1Patch2"; + + boundingBox.setCenter(1.0f, 92.78813f, 1.0f); + + terrainQuad.setPatchChildren(testHeightmap); + + assertTrue(terrainQuad.getChild(patch2) instanceof TerrainPatch); + TerrainPatch p1 = (TerrainPatch) terrainQuad.getChild(patch2); + + assertEquals(patch2, p1.getName()); + assertEquals(v2f.set(-1.0f, 1.0f), p1.getOffset()); + assertEquals(1.0f, p1.getOffsetAmount(), 0.0f); + assertEquals(v3f.add(-2.0f, 0.0f, 0.0f), p1.getLocalTranslation()); + assertEquals(9, p1.getHeightMap().length); + assertEquals(5, p1.getTotalSize()); + assertEquals(2, p1.getQuadrant()); + assertEquals(boundingBox.getCenter(), p1.getModelBound().getCenter()); + } + + @Test + public void testSetPatchChildren3() { + String patch3 = "terrain_1Patch3"; + + boundingBox.setCenter(1.0f, 64.86637f, 1.0f); + + terrainQuad.setPatchChildren(testHeightmap); + + assertTrue(terrainQuad.getChild(patch3) instanceof TerrainPatch); + TerrainPatch p1 = (TerrainPatch) terrainQuad.getChild(patch3); + + assertEquals(patch3, p1.getName()); + assertEquals(v2f.set(1.0f, -1.0f), p1.getOffset()); + assertEquals(1.0f, p1.getOffsetAmount(), 0.0f); + assertEquals(v3f.add(0.0f, 0.0f, -2.0f), p1.getLocalTranslation()); + assertEquals(9, p1.getHeightMap().length); + assertEquals(5, p1.getTotalSize()); + assertEquals(3, p1.getQuadrant()); + assertEquals(boundingBox.getCenter(), p1.getModelBound().getCenter()); + } + + @Test + public void testSetPatchChildren4() { + String patch4 = "terrain_1Patch4"; + + boundingBox.setCenter(1.0f, 180.92175f, 1.0f); + + terrainQuad.setPatchChildren(testHeightmap); + + assertTrue(terrainQuad.getChild(patch4) instanceof TerrainPatch); + TerrainPatch p1 = (TerrainPatch) terrainQuad.getChild(patch4); + + assertEquals(patch4, p1.getName()); + assertEquals(v2f.set(1.0f, 1.0f), p1.getOffset()); + assertEquals(1.0f, p1.getOffsetAmount(), 0.0f); + assertEquals(v3f.add(0.0f, 0.0f, 0.0f), p1.getLocalTranslation()); + assertEquals(9, p1.getHeightMap().length); + assertEquals(5, p1.getTotalSize()); + assertEquals(4, p1.getQuadrant()); + assertEquals(boundingBox.getCenter(), p1.getModelBound().getCenter()); + } + + /** + * Tests the method getHeightmapHeight(int x, int z). + * An extra internal class QuadrantFinder has been created to find the + * corresponding quadrant for the given coordinates. + */ + @Test + public void testGetHeightmapHeight() { + assertEquals(0.0f, terrainQuad.getHeightmapHeight(6, 6), 0.0f); + assertEquals(testHeightmap[0], terrainQuad.getHeightmapHeight(0, 0), 0.0f); + children = new FakeTerrainQuad[3]; + for (int i = 0; i < 3; i++) { + children[i] = new FakeTerrainQuad(); + } + + parentTerrainQuad = new FakeTerrainQuad(); + parentTerrainQuad.size = 10; + fakeCreateQuad(parentTerrainQuad, children); + + assertEquals(Float.NaN, parentTerrainQuad.getHeightmapHeight(5, 3), 0.0f); + + children = new FakeTerrainQuad[2]; + for (int i = 0; i < 2; i++) { + children[i] = new FakeTerrainQuad(); + } + parentTerrainQuad = new FakeTerrainQuad(); + parentTerrainQuad.size = 50; + fakeCreateQuad(parentTerrainQuad, children); + + assertEquals(Float.NaN, parentTerrainQuad.getHeightmapHeight(5, 49), 0.0f); + + + } + + /** + * Tests the method getMeshNormal(int x, int z). + * An extra internal class QuadrantFinder has been created to find the + * corresponding quadrant for the given coordinates. + */ + @Test + public void testGetMeshNormal() { + Vector3f v1 = new Vector3f(-0.7327255f, 0.043074645f, -0.67915976f); + + assertEquals(null, terrainQuad.getMeshNormal(10, 10)); + assertEquals(v1, terrainQuad.getMeshNormal(0, 0)); + children = new FakeTerrainQuad[3]; + for (int i = 0; i < 3; i++) { + children[i] = new FakeTerrainQuad(); + } + + parentTerrainQuad = new FakeTerrainQuad(); + parentTerrainQuad.size = 10; + fakeCreateQuad(parentTerrainQuad, children); + + assertEquals(null, parentTerrainQuad.getMeshNormal(5, 3)); + + children = new FakeTerrainQuad[2]; + for (int i = 0; i < 2; i++) { + children[i] = new FakeTerrainQuad(); + } + parentTerrainQuad = new FakeTerrainQuad(); + parentTerrainQuad.size = 50; + fakeCreateQuad(parentTerrainQuad, children); + + assertEquals(null, parentTerrainQuad.getMeshNormal(5, 49)); + } + + /** + * Tests the method getHeight(int x, int z, float xm, float zm). + * This method is being tested to make sure the private method + * findMatchingChild(int x, int z) after its refactoring in which + * an extra internal class QuadrantFinder has been created to find the + * corresponding quadrant for the given coordinates. + */ + @Test + public void getHeight() { + assertEquals(Float.NaN, terrainQuad.getHeight(10, 10, 10.0f, 10.0f), 0.0f); + assertEquals(2.9181213f, terrainQuad.getHeight(0, 0, 0.0f, 0.0f), 0.0f); + + children = new FakeTerrainQuad[3]; + for (int i = 0; i < 3; i++) { + children[i] = new FakeTerrainQuad(); + } + + parentTerrainQuad = new FakeTerrainQuad(); + parentTerrainQuad.size = 10; + fakeCreateQuad(parentTerrainQuad, children); + + assertEquals(Float.NaN, parentTerrainQuad.getHeight(0, 0, 0.0f, 0.0f), 0.0f); + } + + + /** + * Test for TerrainQuads fixEdges method. + * Both situations of updated and non-updated LODs are covered. + * A typical situation is covered with assertions. + */ + @Test + public void testFixEdges() { + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(2, ""); + HashMap updated = new HashMap<>(); + + assertNotNull(root.getChildren()); + + // Create UTPs and add it to the updated var + for (int i = 1; i <= root.getQuad(1).getChildren().size(); i++) { + UpdatedTerrainPatch utp = new UpdatedTerrainPatch(root.getQuad(1).getPatch(i)); + updated.put(root.getQuad(1).getPatch(i).getName(), utp); + } + + // Copy keys + Set oldKeyset = new HashSet<>(); + for (String s : updated.keySet()) { + oldKeyset.add(s); + } + + // Without any changes in LOD, keyset should remain the same + root.fixEdges(updated); + assertTrue(updated.keySet().equals(oldKeyset)); + + // Change LOD for all patches in quad 1. + for (int i = root.getQuad(1).getChildren().size(); i > 0; i--) { + UpdatedTerrainPatch utp = updated.get(root.getQuad(1).getPatch(i).getName()); + utp.setPreviousLod(1); // Dummy value + utp.setNewLod(2); // Dummy value + } + + // Copy keys + oldKeyset.clear(); + for (String s : updated.keySet()) { + oldKeyset.add(s); + } + + root.fixEdges(updated); + + // Make sure new keyset is different + assertFalse(updated.keySet().equals(oldKeyset)); + + // Extract newly updated keys + updated.keySet().removeAll(oldKeyset); + + // Assert new keys + assertTrue(updated.keySet().contains("21")); + assertTrue(updated.keySet().contains("23")); + assertTrue(updated.keySet().contains("31")); + assertTrue(updated.keySet().contains("32")); + } + + /** + * Test for TerrainQuads deprecated findNeighboursLod method. + * It tests for both an empty UpdatedTerrainPatch set and a full one. + * Assertions are made to verify that the LODs are set correctly. + */ + @Test + public void testFindNeighboursLod() { + FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(2, ""); + HashMap updated = new HashMap<>(); + + assertNotNull(root.getChildren()); + + // Call method with empty setof UTPs + root.findNeighboursLod(updated); + + // Check if all patches are updated + for (int i = 1; i <= root.getChildren().size(); i++) { + for (int j = 1; j <= root.getQuad(i).getChildren().size(); j++) { + TerrainPatch tp = root.getQuad(i).getPatch(j); + assertTrue(updated.containsKey(tp.getName())); + } + } + + // Set random LOD to ensure proper assertions. + for (int i = 1; i <= root.getChildren().size(); i++) { + for (int j = 1; j <= root.getQuad(i).getChildren().size(); j++) { + UpdatedTerrainPatch utp = updated.get(root.getQuad(i).getPatch(j).getName()); + utp.setPreviousLod((int)(Math.random() * 100)); // Dummy value + utp.setNewLod((int)(Math.random() * 100)); // Dummy value + } + } + + root.findNeighboursLod(updated); + + // Check if all patches are updated. + for (int i = 1; i <= root.getChildren().size(); i++) { + for (int j = 1; j <= root.getQuad(i).getChildren().size(); j++) { + TerrainPatch tp = root.getQuad(i).getPatch(j); + UpdatedTerrainPatch utp = updated.get(tp.getName()); + + TerrainPatch left = tp.findPatch(DIR_LEFT); + if (left != null) { + UpdatedTerrainPatch leftUtp = updated.get(left.getName()); + assertEquals(utp.getLeftLod(), leftUtp.getNewLod()); + assertEquals(leftUtp.getRightLod(), utp.getNewLod()); + } + + TerrainPatch right = tp.findPatch(DIR_RIGHT); + if (right != null) { + UpdatedTerrainPatch rightUtp = updated.get(right.getName()); + assertEquals(utp.getRightLod(), rightUtp.getNewLod()); + assertEquals(rightUtp.getLeftLod(), utp.getNewLod()); + } + + TerrainPatch top = tp.findPatch(DIR_TOP); + if (top != null) { + UpdatedTerrainPatch topUtp = updated.get(top.getName()); + assertEquals(utp.getTopLod(), topUtp.getNewLod()); + assertEquals(topUtp.getBottomLod(), utp.getNewLod()); + } + + TerrainPatch down = tp.findPatch(DIR_DOWN); + if (down != null) { + UpdatedTerrainPatch downUtp = updated.get(down.getName()); + assertEquals(utp.getBottomLod(), downUtp.getNewLod()); + assertEquals(downUtp.getTopLod(), utp.getNewLod()); + } + } + } + } +} diff --git a/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/TestNeighbourFinder.java b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/TestNeighbourFinder.java new file mode 100644 index 0000000000..ecbc31c0d0 --- /dev/null +++ b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/TestNeighbourFinder.java @@ -0,0 +1,61 @@ +package com.jme3.terrain.geomipmap; + +public class TestNeighbourFinder implements NeighbourFinder { + + TerrainQuad quad1, quad2, quad3, quad4; + + public TestNeighbourFinder(TerrainQuad quad1, TerrainQuad quad2, TerrainQuad quad3, TerrainQuad quad4) { + this.quad1 = quad1; + this.quad2 = quad2; + this.quad3 = quad3; + this.quad4 = quad4; + } + + @Override + public TerrainQuad getRightQuad(TerrainQuad center) { + if (center == quad1) { + return quad3; + } + if (center == quad2) { + return quad4; + } + + return null; + } + + @Override + public TerrainQuad getLeftQuad(TerrainQuad center) { + if (center == quad3) { + return quad1; + } + if (center == quad4) { + return quad2; + } + + return null; + } + + @Override + public TerrainQuad getTopQuad(TerrainQuad center) { + if (center == quad2) { + return quad1; + } + if (center == quad4) { + return quad3; + } + + return null; + } + + @Override + public TerrainQuad getDownQuad(TerrainQuad center) { + if (center == quad1) { + return quad2; + } + if (center == quad3) { + return quad4; + } + + return null; + } +} diff --git a/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/UpdatedTerrainPatchTest.java b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/UpdatedTerrainPatchTest.java new file mode 100644 index 0000000000..eca8a53c5b --- /dev/null +++ b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/UpdatedTerrainPatchTest.java @@ -0,0 +1,59 @@ +package com.jme3.terrain.geomipmap; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class UpdatedTerrainPatchTest { + private final int DIR_RIGHT = 0, DIR_DOWN = 1, DIR_LEFT = 2, DIR_TOP = 3; + + @Test + public void testSetLeftLod() { + TerrainPatch tp = new TerrainPatch(); + UpdatedTerrainPatch utp = new UpdatedTerrainPatch(tp); + utp.setLeftLod(1); + assertEquals(utp.getLeftLod(), 1); + } + + @Test + public void testSetTopLod() { + TerrainPatch tp = new TerrainPatch(); + UpdatedTerrainPatch utp = new UpdatedTerrainPatch(tp); + utp.setTopLod(1); + assertEquals(utp.getTopLod(), 1); + } + + @Test + public void testSetRightLod() { + TerrainPatch tp = new TerrainPatch(); + UpdatedTerrainPatch utp = new UpdatedTerrainPatch(tp); + utp.setRightLod(1); + assertEquals(utp.getRightLod(), 1); + } + + @Test + public void testSetBottomLod() { + TerrainPatch tp = new TerrainPatch(); + UpdatedTerrainPatch utp = new UpdatedTerrainPatch(tp); + utp.setBottomLod(1); + assertEquals(utp.getBottomLod(), 1); + } + + @Test + public void testSetLod() { + TerrainPatch tp = new TerrainPatch(); + UpdatedTerrainPatch utp = new UpdatedTerrainPatch(tp); + + utp.setLod(1, DIR_DOWN); + assertEquals(utp.getBottomLod(), 1); + + utp.setLod(2, DIR_LEFT); + assertEquals(utp.getLeftLod(), 2); + + utp.setLod(3, DIR_RIGHT); + assertEquals(utp.getRightLod(), 3); + + utp.setLod(4, DIR_TOP); + assertEquals(utp.getTopLod(), 4); + } +}