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