diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainTest.java index 7eff18b8f9..a0f6572351 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainTest.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainTest.java @@ -157,13 +157,14 @@ public void simpleInitApp() { */ terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); - control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier + control.setLodCalculator(new DistanceLodCalculator(65, 2.7f)); // patch size, and a multiplier terrain.addControl(control); terrain.setMaterial(matRock); terrain.setLocalTranslation(0, -100, 0); terrain.setLocalScale(2f, 0.5f, 2f); rootNode.attachChild(terrain); + DirectionalLight light = new DirectionalLight(); light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize()); rootNode.addLight(light); @@ -187,6 +188,7 @@ private void setupKeys() { inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P)); inputManager.addListener(actionListener, "triPlanar"); } + private ActionListener actionListener = new ActionListener() { public void onAction(String name, boolean pressed, float tpf) { diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java index 995bc8f132..8733716643 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java @@ -35,20 +35,21 @@ import com.jme3.renderer.Camera; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * An extension of the TerrainLodControl that handles - * multiple terrains at once. This is to be used if you + * multiple terrains at once. This is to be used if you * have your own tiling/paging terrain system, such as * TerrainGrid. - * + * * @author Brent Owens */ public class MultiTerrainLodControl extends TerrainLodControl { - + List terrains = new ArrayList(); private List addedTerrains = new ArrayList(); private List removedTerrains = new ArrayList(); @@ -64,7 +65,7 @@ public MultiTerrainLodControl(Camera camera) { this.cameras = cams; lodCalculator = new DistanceLodCalculator(65, 2.7f); } - + /** * Add a terrain that will have its LOD handled by this control. * It will be added next update run. You should only call this from @@ -73,7 +74,7 @@ public MultiTerrainLodControl(Camera camera) { public void addTerrain(TerrainQuad tq) { addedTerrains.add(tq); } - + /** * Add a terrain that will no longer have its LOD handled by this control. * It will be removed next update run. You should only call this from @@ -82,12 +83,12 @@ public void addTerrain(TerrainQuad tq) { public void removeTerrain(TerrainQuad tq) { removedTerrains.add(tq); } - + @Override protected UpdateLOD getLodThread(List locations, LodCalculator lodCalculator) { return new UpdateMultiLOD(locations, lodCalculator); } - + @Override protected void prepareTerrain() { if (!addedTerrains.isEmpty()) { @@ -97,57 +98,57 @@ protected void prepareTerrain() { } addedTerrains.clear(); } - + if (!removedTerrains.isEmpty()) { terrains.removeAll(removedTerrains); removedTerrains.clear(); } - + for (TerrainQuad terrain : terrains) terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely } - + /** * Overrides the parent UpdateLOD runnable to process * multiple terrains. */ protected class UpdateMultiLOD extends UpdateLOD { - - + + protected UpdateMultiLOD(List camLocations, LodCalculator lodCalculator) { super(camLocations, lodCalculator); } - + @Override public HashMap call() throws Exception { - + setLodCalcRunning(true); - - HashMap updated = new HashMap(); - + + HashMap updated = new HashMap(); + for (TerrainQuad terrainQuad : terrains) { // go through each patch and calculate its LOD based on camera distance - terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here + terrainQuad.hasLodChanged(camLocations, updated, lodCalculator); // 'updated' gets populated here } - + for (TerrainQuad terrainQuad : terrains) { // then calculate the neighbour LOD values for seaming terrainQuad.findNeighboursLod(updated); } - + for (TerrainQuad terrainQuad : terrains) { // check neighbour quads that need their edges seamed terrainQuad.fixEdges(updated); } - + for (TerrainQuad terrainQuad : terrains) { // perform the edge seaming, if it requires it terrainQuad.reIndexPages(updated, lodCalculator.usesVariableLod()); } - + //setUpdateQuadLODs(updated); // set back to main ogl thread setLodCalcRunning(false); - + return updated; } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java index 8ad2425ada..e17b37dcfa 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java @@ -46,16 +46,12 @@ import com.jme3.terrain.Terrain; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; + import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -68,16 +64,16 @@ * NOTE: right now it just uses the first camera passed in, * in the future it will use all of them to determine what * LOD to set. - * + *

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

* When the control or the terrain are removed from the scene, you should call * TerrainLodControl.detachAndCleanUpControl() to remove any threads it created * to handle the LOD processing. If you supply your own executor service, then * you have to handle its thread termination yourself. - * + * * @author Brent Owens */ public class TerrainLodControl extends AbstractControl { @@ -88,17 +84,17 @@ public class TerrainLodControl extends AbstractControl { protected LodCalculator lodCalculator; private boolean hasResetLod = false; // used when enabled is set to false - private HashMap updatedPatches; + private HashMap updatedPatches; private final Object updatePatchesLock = new Object(); - + protected List lastCameraLocations; // used for LOD calc private AtomicBoolean lodCalcRunning = new AtomicBoolean(false); private int lodOffCount = 0; - + protected ExecutorService executor; protected Future> indexer; private boolean forceUpdate = true; - + public TerrainLodControl() { } @@ -109,9 +105,10 @@ public TerrainLodControl(Terrain terrain, Camera camera) { this.cameras = cams; lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator } - + /** * Only uses the first camera right now. + * * @param terrain to act upon (must be a Spatial) * @param cameras one or more cameras to reference for LOD calc */ @@ -132,7 +129,7 @@ protected void controlRender(RenderManager rm, ViewPort vp) { public void setExecutor(ExecutorService executor) { this.executor = executor; } - + protected ExecutorService createExecutorService() { return Executors.newSingleThreadExecutor(new ThreadFactory() { public Thread newThread(Runnable r) { @@ -143,14 +140,14 @@ public Thread newThread(Runnable r) { } }); } - + @Override protected void controlUpdate(float tpf) { //list of cameras for when terrain supports multiple cameras (ie split screen) if (lodCalculator == null) return; - + if (!enabled) { if (!hasResetLod) { // this will get run once @@ -158,7 +155,7 @@ protected void controlUpdate(float tpf) { lodCalculator.turnOffLod(); } } - + if (cameras != null) { cameraLocations.clear(); for (Camera c : cameras) // populate them @@ -168,7 +165,7 @@ protected void controlUpdate(float tpf) { updateLOD(cameraLocations, lodCalculator); } } - + /** * Call this when you remove the terrain or this control from the scene. * It will clear up any threads it had. @@ -181,10 +178,10 @@ public void detachAndCleanUpControl() { // do all of the LOD calculations protected void updateLOD(List locations, LodCalculator lodCalculator) { - if(getSpatial() == null){ + if (getSpatial() == null) { return; } - + // update any existing ones that need updating updateQuadLODs(); @@ -194,17 +191,16 @@ protected void updateLOD(List locations, LodCalculator lodCalculator) return; else lodOffCount++; - } else + } else lodOffCount = 0; - + if (lastCameraLocations != null) { if (!forceUpdate && lastCameraLocationsTheSame(locations) && !lodCalculator.isLodOff()) return; // don't update if in same spot else lastCameraLocations = cloneVectorList(locations); forceUpdate = false; - } - else { + } else { lastCameraLocations = cloneVectorList(locations); return; } @@ -216,9 +212,9 @@ protected void updateLOD(List locations, LodCalculator lodCalculator) if (executor == null) executor = createExecutorService(); - + prepareTerrain(); - + UpdateLOD updateLodThread = getLodThread(locations, lodCalculator); indexer = executor.submit(updateLodThread); } @@ -230,12 +226,12 @@ protected void updateLOD(List locations, LodCalculator lodCalculator) public void forceUpdate() { this.forceUpdate = true; } - + protected void prepareTerrain() { - TerrainQuad terrain = (TerrainQuad)getSpatial(); + TerrainQuad terrain = (TerrainQuad) getSpatial(); terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely } - + protected UpdateLOD getLodThread(List locations, LodCalculator lodCalculator) { return new UpdateLOD(locations, lodCalculator); } @@ -247,7 +243,7 @@ private void updateQuadLODs() { if (indexer != null) { if (indexer.isDone()) { try { - + HashMap updated = indexer.get(); if (updated != null) { // do the actual geometry update here @@ -255,7 +251,7 @@ private void updateQuadLODs() { utp.updateAll(); } } - + } catch (InterruptedException ex) { Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex); } catch (ExecutionException ex) { @@ -266,12 +262,12 @@ private void updateQuadLODs() { } } } - + private boolean lastCameraLocationsTheSame(List locations) { boolean theSame = true; for (Vector3f l : locations) { for (Vector3f v : lastCameraLocations) { - if (!v.equals(l) ) { + if (!v.equals(l)) { theSame = false; return false; } @@ -279,7 +275,7 @@ private boolean lastCameraLocationsTheSame(List locations) { } return theSame; } - + protected synchronized boolean isLodCalcRunning() { return lodCalcRunning.get(); } @@ -290,17 +286,12 @@ protected synchronized void setLodCalcRunning(boolean running) { private List cloneVectorList(List locations) { List cloned = new ArrayList(); - for(Vector3f l : locations) + for (Vector3f l : locations) cloned.add(l.clone()); return cloned; } - - - - - - + public Control cloneForSpatial(Spatial spatial) { if (spatial instanceof Terrain) { List cameraClone = new ArrayList(); @@ -321,7 +312,7 @@ public void setCamera(Camera camera) { cams.add(camera); setCameras(cams); } - + public void setCameras(List cameras) { this.cameras = cameras; cameraLocations.clear(); @@ -349,7 +340,7 @@ public LodCalculator getLodCalculator() { public void setLodCalculator(LodCalculator lodCalculator) { this.lodCalculator = lodCalculator; } - + @Override public void setEnabled(boolean enabled) { this.enabled = enabled; @@ -361,12 +352,12 @@ public void setEnabled(boolean enabled) { lodCalculator.turnOnLod(); } } - - + + /** * Calculates the LOD of all child terrain patches. */ - protected class UpdateLOD implements Callable> { + protected class UpdateLOD implements Callable> { protected List camLocations; protected LodCalculator lodCalculator; @@ -382,19 +373,19 @@ public HashMap call() throws Exception { //} setLodCalcRunning(true); - TerrainQuad terrainQuad = (TerrainQuad)getSpatial(); - + TerrainQuad terrainQuad = (TerrainQuad) getSpatial(); + // go through each patch and calculate its LOD based on camera distance - HashMap updated = new HashMap(); - boolean lodChanged = terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here + HashMap updated = new HashMap(); + boolean lodChanged = terrainQuad.hasLodChanged(camLocations, updated, lodCalculator); // 'updated' gets populated here if (!lodChanged) { // not worth updating anything else since no one's LOD changed setLodCalcRunning(false); return null; } - - + + // then calculate its neighbour LOD values for seaming in the shader terrainQuad.findNeighboursLod(updated); @@ -405,7 +396,7 @@ public HashMap call() throws Exception { //setUpdateQuadLODs(updated); // set back to main ogl thread setLodCalcRunning(false); - + return updated; } } @@ -414,7 +405,7 @@ public HashMap call() throws Exception { public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); - oc.write((Node)terrain, "terrain", null); + oc.write((Node) terrain, "terrain", null); oc.write(lodCalculator, "lodCalculator", null); } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java index fecf747de3..076ba8ef1d 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 @@ -625,7 +625,6 @@ protected TerrainPatch createQuadPatch(TerrainQuad terrainQuad, int patchNumber, origin = new Vector3f(-halfSize * terrainQuad.stepScale.x, 0, 0); tempOffset.x += origin.x / 2; tempOffset.y += quarterSize * terrainQuad.stepScale.z; - System.out.println(tempOffset); break; case 3: origin = new Vector3f(0, 0, -halfSize * terrainQuad.stepScale.z); 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 64be20425b..6aa2f1106e 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 @@ -108,6 +108,10 @@ * @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) @@ -364,7 +368,7 @@ public int getNumMajorSubdivisions() { } - protected boolean calculateLod(List location, HashMap updates, LodCalculator lodCalculator) { + protected boolean hasLodChanged(List location, HashMap updates, LodCalculator lodCalculator) { boolean lodChanged = false; @@ -373,7 +377,7 @@ protected boolean calculateLod(List location, HashMap= 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) { @@ -810,8 +814,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 @@ -857,96 +861,47 @@ 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; } @@ -978,41 +933,20 @@ private class QuadrantChild { } } + /** + * 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; } @@ -1072,7 +1006,7 @@ 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)); @@ -1144,6 +1078,7 @@ protected class LocationHeight { this.z = z; this.h = h; } + } protected void setHeight(List locations, boolean overrideHeight) { @@ -1866,5 +1801,62 @@ else if (((TerrainPatch) s).getQuadrant() == 4) return hm; } + + public class QuadrantFinder { + private int x; + private int z; + private Spatial spat; + private int col; + private int row; + private boolean match; + + public QuadrantFinder(int x, int z) { + this.x = x; + this.z = z; + } + + public boolean isMatch() { + return this.match; + } + + public boolean invoke() { + int quad = findQuadrant(this.x, this.z); + int split = (size + 1) >> 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/test/java/com/jme3/terrain/geomipmap/TerrainQuadTest.java b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/TerrainQuadTest.java index ef81661992..4e172fbaae 100644 --- a/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/TerrainQuadTest.java +++ b/jme3-terrain/src/test/java/com/jme3/terrain/geomipmap/TerrainQuadTest.java @@ -1,30 +1,73 @@ package com.jme3.terrain.geomipmap; -import static org.junit.Assert.*; - +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.Test; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static org.junit.Assert.*; + public class TerrainQuadTest { + 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++) { + for (int i = 0; i < 4; i++) { children[i] = new FakeTerrainQuad(); + // children[i] = new TerrainQuad(); + tpChildren[i] = new TerrainPatch(); } - parentTerrainQuad = new FakeTerrainQuad(); - fakeCreateQuad(parentTerrainQuad, children); + // 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(); + } 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]); + //parent.getQuad(i).attachChild((tpChildren[i])); } } @@ -32,6 +75,7 @@ private void fakeCreateQuad(FakeTerrainQuad parent, FakeTerrainQuad[] children) * 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 */ @@ -41,7 +85,7 @@ private Spatial createNestedQuad(int nestLevel) { } FakeTerrainQuad parent = new FakeTerrainQuad(); - for(int i = 0; i < 4; i++) { + for (int i = 0; i < 4; i++) { Spatial child = createNestedQuad(nestLevel - 1); if (child instanceof TerrainPatch) { @@ -71,24 +115,224 @@ public void testNestStructure() { FakeTerrainQuad root = (FakeTerrainQuad) createNestedQuad(1); assertEquals(root.getChildren().size(), 4); - for(int i = 0; i < 4; i++) { + 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++) { + 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); +// } + + + /** + * 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 users of the LodCalculator interface. + * The actual lodCalculator is defined in the calculateLod method of these childs. + */ @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); + public void testCalculateLod() { + 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); + } + + }