Переглянути джерело

Softened terrain API to allow for different tiling implementations. Added MultiTerrainLodControl

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9434 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
bre..ns 13 роки тому
батько
коміт
33a69d4536

+ 8 - 3
engine/src/terrain/com/jme3/terrain/geomipmap/LODGeomap.java

@@ -154,6 +154,9 @@ public class LODGeomap extends GeoMap {
      */
     public IntBuffer writeIndexArrayLodDiff(IntBuffer store, int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod) {
 
+        //if (true)
+        //return writeIndexArrayLodVariable(store, lod, height, lod, lod, lod);
+        
         IntBuffer buffer2 = store;
         int numIndexes = calculateNumIndexesLodDiff(lod);
         if (store == null) {
@@ -275,7 +278,7 @@ public class LODGeomap extends GeoMap {
                 buffer.put(idx);
                 idx = (row + 2 * lod) * getWidth();
                 buffer.put(idx);
-                if (row < getWidth() - lod - 2 - 1) { //if not the last one
+                if (row < getWidth() - 1 - 2 * lod) { //if not the last one
                     idx = (row + 2 * lod) * getWidth() + lod;
                     buffer.put(idx);
                     idx = (row + 2 * lod) * getWidth();
@@ -917,10 +920,12 @@ public class LODGeomap extends GeoMap {
 
         public void put(int value) {
             try {
-                delegate.put(value);
                 count++;
+                if (count > delegate.limit())
+                    throw new BufferOverflowException();
+                delegate.put(value);
             } catch (BufferOverflowException e) {
-                //System.out.println("err buffer size: "+delegate.capacity());
+                System.out.println("err buffer size: "+delegate.capacity());
             }
         }
 

+ 127 - 0
engine/src/terrain/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java

@@ -0,0 +1,127 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.terrain.geomipmap;
+
+import com.jme3.math.Vector3f;
+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 
+ * have your own tiling/paging terrain system, such as
+ * TerrainGrid.
+ * 
+ * @author Brent Owens
+ */
+public class MultiTerrainLodControl extends TerrainLodControl {
+    
+    List<TerrainQuad> terrains = new ArrayList<TerrainQuad>();
+    private List<TerrainQuad> addedTerrains = new ArrayList<TerrainQuad>();
+    private List<TerrainQuad> removedTerrains = new ArrayList<TerrainQuad>();
+
+    public MultiTerrainLodControl(List<Camera> cameras) {
+        this.cameras = cameras;
+        lodCalculator = new DistanceLodCalculator(65, 2.7f);
+    }
+
+    public MultiTerrainLodControl(Camera camera) {
+        List<Camera> cams = new ArrayList<Camera>();
+        cams.add(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
+     * the render thread.
+     */
+    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
+     * the render thread.
+     */
+    public void removeTerrain(TerrainQuad tq) {
+        removedTerrains.remove(tq);
+    }
+    
+    @Override
+    protected UpdateLOD getLodThread(List<Vector3f> locations, LodCalculator lodCalculator) {
+        return new UpdateMultiLOD(locations, lodCalculator);
+    }
+    
+    @Override
+    protected void prepareTerrain() {
+        if (!addedTerrains.isEmpty()) {
+            for (TerrainQuad t : addedTerrains) {
+                if (!terrains.contains(t))
+                    terrains.add(t);
+            }
+            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<Vector3f> camLocations, LodCalculator lodCalculator) {
+            super(camLocations, lodCalculator);
+        }
+        
+        @Override
+        public HashMap<String, UpdatedTerrainPatch> call() throws Exception {
+            
+            setLodCalcRunning(true);
+            
+            HashMap<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>();
+            
+            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
+            }
+            
+            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;
+        }
+    }
+}

+ 13 - 1
engine/src/terrain/com/jme3/terrain/geomipmap/NeighbourFinder.java

@@ -13,7 +13,7 @@ package com.jme3.terrain.geomipmap;
  * With this you can have a parent, control or spatial, that manages a group of
  * TerrainQuads by linking them together through these four methods.
  * 
- * The general orientation of TerrainQuads and their sun-quads is as such:
+ * The general orientation of TerrainQuads and their sub-quads is as such:
  * 
  * 
  *  +-- x+ ---->
@@ -32,11 +32,23 @@ package com.jme3.terrain.geomipmap;
  */
 public interface NeighbourFinder {
     
+    /**
+     * Get the TerrainQuad to the right of the supplied 'center' quad.
+     */
     public TerrainQuad getRightQuad(TerrainQuad center);
     
+    /**
+     * Get the TerrainQuad to the left of the supplied 'center' quad.
+     */
     public TerrainQuad getLeftQuad(TerrainQuad center);
     
+    /**
+     * Get the TerrainQuad above the supplied 'center' quad.
+     */
     public TerrainQuad getTopQuad(TerrainQuad center);
     
+    /**
+     * Get the TerrainQuad below the supplied 'center' quad.
+     */
     public TerrainQuad getDownQuad(TerrainQuad center);
 }

+ 64 - 36
engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java

@@ -50,9 +50,15 @@ 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.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 /**
  * Tells the terrain to update its Level of Detail.
@@ -72,19 +78,20 @@ import java.util.concurrent.ThreadFactory;
 public class TerrainLodControl extends AbstractControl {
 
     private Terrain terrain;
-    private List<Camera> cameras;
+    protected List<Camera> cameras;
     private List<Vector3f> cameraLocations = new ArrayList<Vector3f>();
-    private LodCalculator lodCalculator;
+    protected LodCalculator lodCalculator;
     private boolean hasResetLod = false; // used when enabled is set to false
 
     private HashMap<String,UpdatedTerrainPatch> updatedPatches;
     private final Object updatePatchesLock = new Object();
     
     protected List<Vector3f> lastCameraLocations; // used for LOD calc
-    private boolean lodCalcRunning = false;
+    private AtomicBoolean lodCalcRunning = new AtomicBoolean(false);
     private int lodOffCount = 0;
     
     protected ExecutorService executor;
+    protected Future<HashMap<String, UpdatedTerrainPatch>> indexer;
     
     public TerrainLodControl() {
     }
@@ -177,6 +184,7 @@ public class TerrainLodControl extends AbstractControl {
         if (isLodCalcRunning()) {
             return;
         }
+        setLodCalcRunning(true);
 
         //if (getParent() instanceof TerrainQuad) {
         //    return; // we just want the root quad to perform this.
@@ -185,25 +193,47 @@ public class TerrainLodControl extends AbstractControl {
         if (executor == null)
             executor = createExecutorService();
         
+        prepareTerrain();
+        
+        UpdateLOD updateLodThread = getLodThread(locations, lodCalculator);
+        indexer = executor.submit(updateLodThread);
+    }
+    
+    protected void prepareTerrain() {
         TerrainQuad terrain = (TerrainQuad)getSpatial();
         terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely
-        
-        UpdateLOD updateLodThread = new UpdateLOD(locations, lodCalculator);
-        executor.execute(updateLodThread);
     }
     
-    private void setUpdateQuadLODs(HashMap<String,UpdatedTerrainPatch> updated) {
-        synchronized (updatePatchesLock) {
-            updatedPatches = updated;
-        }
+    protected UpdateLOD getLodThread(List<Vector3f> locations, LodCalculator lodCalculator) {
+        return new UpdateLOD(locations, lodCalculator);
     }
 
     /**
      * Back on the ogl thread: update the terrain patch geometries
-     * @param updatedPatches to be updated
      */
     private void updateQuadLODs() {
-        synchronized (updatePatchesLock) {
+        if (indexer != null) {
+            if (indexer.isDone()) {
+                try {
+                    
+                    HashMap<String, UpdatedTerrainPatch> updated = indexer.get();
+                    if (updated != null) {
+                        // do the actual geometry update here
+                        for (UpdatedTerrainPatch utp : updated.values()) {
+                            utp.updateAll();
+                        }
+                    }
+                    
+                } catch (InterruptedException ex) {
+                    Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex);
+                } catch (ExecutionException ex) {
+                    Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex);
+                } finally {
+                    indexer = null;
+                }
+            }
+        }
+        /*synchronized (updatePatchesLock) {
             
             if (updatedPatches == null || updatedPatches.isEmpty())
                 return;
@@ -213,13 +243,13 @@ public class TerrainLodControl extends AbstractControl {
                 utp.updateAll();
             }
 
-            updatedPatches.clear();
-        }
+            updatedPatches = null;
+        }*/
     }
     
-    public boolean hasPatchesToUpdate() {
-        return updatedPatches != null && !updatedPatches.isEmpty();
-    }
+    //public boolean hasPatchesToUpdate() {
+    //    return updatedPatches != null && !updatedPatches.isEmpty();
+    //}
     
     private boolean lastCameraLocationsTheSame(List<Vector3f> locations) {
         boolean theSame = true;
@@ -234,12 +264,12 @@ public class TerrainLodControl extends AbstractControl {
         return theSame;
     }
     
-    private synchronized boolean isLodCalcRunning() {
-        return lodCalcRunning;
+    protected synchronized boolean isLodCalcRunning() {
+        return lodCalcRunning.get();
     }
 
-    private synchronized void setLodCalcRunning(boolean running) {
-        lodCalcRunning = running;
+    protected synchronized void setLodCalcRunning(boolean running) {
+        lodCalcRunning.set(running);
     }
 
     private List<Vector3f> cloneVectorList(List<Vector3f> locations) {
@@ -320,22 +350,20 @@ public class TerrainLodControl extends AbstractControl {
     /**
      * Calculates the LOD of all child terrain patches.
      */
-    private class UpdateLOD implements Runnable {
-        private List<Vector3f> camLocations;
-        private LodCalculator lodCalculator;
+    protected class UpdateLOD implements Callable<HashMap<String,UpdatedTerrainPatch>> {
+        protected List<Vector3f> camLocations;
+        protected LodCalculator lodCalculator;
 
-        UpdateLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) {
+        protected UpdateLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) {
             this.camLocations = camLocations;
             this.lodCalculator = lodCalculator;
         }
 
-        public void run() {
-            long start = System.currentTimeMillis();
-            if (isLodCalcRunning()) {
-                //System.out.println("thread already running");
-                return;
-            }
-            //System.out.println("spawned thread "+toString());
+        public HashMap<String, UpdatedTerrainPatch> call() throws Exception {
+            //long start = System.currentTimeMillis();
+            //if (isLodCalcRunning()) {
+            //    return null;
+            //}
             setLodCalcRunning(true);
 
             TerrainQuad terrainQuad = (TerrainQuad)getSpatial();
@@ -347,7 +375,7 @@ public class TerrainLodControl extends AbstractControl {
             if (!lodChanged) {
                 // not worth updating anything else since no one's LOD changed
                 setLodCalcRunning(false);
-                return;
+                return null;
             }
             
             
@@ -358,11 +386,11 @@ public class TerrainLodControl extends AbstractControl {
 
             terrainQuad.reIndexPages(updated, lodCalculator.usesVariableLod());
 
-            setUpdateQuadLODs(updated); // set back to main ogl thread
+            //setUpdateQuadLODs(updated); // set back to main ogl thread
 
             setLodCalcRunning(false);
-            //double duration = (System.currentTimeMillis()-start);
-            //System.out.println("terminated in "+duration);
+            
+            return updated;
         }
     }
 

+ 2 - 2
engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java

@@ -80,7 +80,7 @@ import java.util.List;
 public class TerrainPatch extends Geometry {
 
     protected LODGeomap geomap;
-    protected int lod = -1; // this terrain patch's LOD
+    protected int lod = 0; // this terrain patch's LOD
     private int maxLod = -1;
     protected int previousLod = -1;
     protected int lodLeft, lodTop, lodRight, lodBottom; // it's neighbour's LODs
@@ -234,7 +234,7 @@ public class TerrainPatch extends Geometry {
 
         UpdatedTerrainPatch utp = updated.get(getName());
 
-        if (utp != null && (utp.isReIndexNeeded() || utp.isFixEdges()) ) {
+        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();

+ 101 - 63
engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java

@@ -201,6 +201,7 @@ public class TerrainQuad extends Node implements Terrain {
 
     public void setNeighbourFinder(NeighbourFinder neighbourFinder) {
         this.neighbourFinder = neighbourFinder;
+        resetCachedNeighbours();
     }
 
     /**
@@ -360,6 +361,8 @@ public class TerrainQuad extends Node implements Terrain {
                     }
                     TerrainPatch right = patch.rightNeighbour;
                     TerrainPatch down = patch.bottomNeighbour;
+                    TerrainPatch left = patch.leftNeighbour;
+                    TerrainPatch top = patch.topNeighbour;
 
                     UpdatedTerrainPatch utp = updated.get(patch.getName());
                     if (utp == null) {
@@ -370,34 +373,55 @@ public class TerrainQuad extends Node implements Terrain {
                     if (right != null) {
                         UpdatedTerrainPatch utpR = updated.get(right.getName());
                         if (utpR == null) {
-                            utpR = new UpdatedTerrainPatch(right, right.lod);
+                            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, down.lod);
+                            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);
+                        }
+                        utp.setTopLod(utpT.getNewLod());
+                        utpT.setBottomLod(utp.getNewLod());
+                    }
                 }
             }
         }
     }
 
     /**
+     * Reset the cached references of neighbours.
      * TerrainQuad caches neighbours for faster LOD checks.
      * Sometimes you might want to reset this cache (for instance in TerrainGrid)
      */
-    protected void resetCachedNeighbours() {
+    public void resetCachedNeighbours() {
         if (children != null) {
             for (int x = children.size(); --x >= 0;) {
                 Spatial child = children.get(x);
@@ -441,33 +465,41 @@ public class TerrainQuad extends Node implements Terrain {
                         if (right != null) {
                             UpdatedTerrainPatch utpR = updated.get(right.getName());
                             if (utpR == null) {
-                                utpR = new UpdatedTerrainPatch(right, right.lod);
+                                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, down.lod);
+                                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, top.lod);
+                                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, left.lod);
+                                utpL = new UpdatedTerrainPatch(left);
                                 updated.put(utpL.getName(), utpL);
+                                utpL.setNewLod(left.lod);
                             }
+                            utpL.setRightLod(utp.getNewLod());
                             utpL.setFixEdges(true);
                         }
                     }
@@ -1278,6 +1310,8 @@ public class TerrainQuad extends Node implements Terrain {
     }
 
     protected TerrainQuad getQuad(int quad) {
+        if (quad == 0)
+            return this;
         if (children != null)
             for (int x = children.size(); --x >= 0;) {
                 Spatial child = children.get(x);
@@ -1355,7 +1389,7 @@ public class TerrainQuad extends Node implements Terrain {
         else if (tp.getQuadrant() == 4)
             return getPatch(2);
         else if (tp.getQuadrant() == 1) {
-            // find the patch above and ask it for child 2.
+            // find the patch above and ask it for child 3.
             TerrainQuad quad = findLeftQuad();
             if (quad != null)
                 return quad.getPatch(3);
@@ -1370,34 +1404,35 @@ public class TerrainQuad extends Node implements Terrain {
 
     protected TerrainQuad findRightQuad() {
         boolean useFinder = false;
-        if (getParent() == null || !(getParent() instanceof TerrainQuad))
-             if (neighbourFinder == null)
+        if (getParent() == null || !(getParent() instanceof TerrainQuad)) {
+            if (neighbourFinder == null)
                 return null;
             else
                 useFinder = true;
+        }
 
-        TerrainQuad pQuad = (TerrainQuad) getParent();
+        TerrainQuad pQuad = null;
+        if (!useFinder)
+            pQuad = (TerrainQuad) getParent();
 
         if (quadrant == 1)
             return pQuad.getQuad(3);
         else if (quadrant == 2)
             return pQuad.getQuad(4);
         else if (quadrant == 3) {
-            TerrainQuad quad = null;
-            if (useFinder)
-                quad = neighbourFinder.getRightQuad(this);
-            else
-                quad = pQuad.findRightQuad();
+            TerrainQuad quad = pQuad.findRightQuad();
             if (quad != null)
                 return quad.getQuad(1);
         } else if (quadrant == 4) {
-            TerrainQuad quad = null;
-            if (useFinder)
-                quad = neighbourFinder.getRightQuad(this);
-            else
-                quad = pQuad.findRightQuad();
+            TerrainQuad quad = pQuad.findRightQuad();
             if (quad != null)
                 return quad.getQuad(2);
+        } else if (quadrant == 0) {
+            // at the top quad
+            if (useFinder) {
+                TerrainQuad quad = neighbourFinder.getRightQuad(this);
+                return quad;
+            }
         }
 
         return null;
@@ -1405,34 +1440,35 @@ public class TerrainQuad extends Node implements Terrain {
 
     protected TerrainQuad findDownQuad() {
         boolean useFinder = false;
-        if (getParent() == null || !(getParent() instanceof TerrainQuad))
-             if (neighbourFinder == null)
+        if (getParent() == null || !(getParent() instanceof TerrainQuad)) {
+            if (neighbourFinder == null)
                 return null;
             else
                 useFinder = true;
+        }
 
-        TerrainQuad pQuad = (TerrainQuad) getParent();
+        TerrainQuad pQuad = null;
+        if (!useFinder)
+            pQuad = (TerrainQuad) getParent();
 
         if (quadrant == 1)
             return pQuad.getQuad(2);
         else if (quadrant == 3)
             return pQuad.getQuad(4);
         else if (quadrant == 2) {
-            TerrainQuad quad = null;
-            if (useFinder)
-                quad = neighbourFinder.getDownQuad(this);
-            else
-                quad = pQuad.findDownQuad();
+            TerrainQuad quad = pQuad.findDownQuad();
             if (quad != null)
                 return quad.getQuad(1);
         } else if (quadrant == 4) {
-            TerrainQuad quad = null;
-            if (useFinder)
-                quad = neighbourFinder.getDownQuad(this);
-            else
-                quad = pQuad.findDownQuad();
+            TerrainQuad quad = pQuad.findDownQuad();
             if (quad != null)
                 return quad.getQuad(3);
+        } else if (quadrant == 0) {
+            // at the top quad
+            if (useFinder) {
+                TerrainQuad quad = neighbourFinder.getDownQuad(this);
+                return quad;
+            }
         }
 
         return null;
@@ -1440,34 +1476,35 @@ public class TerrainQuad extends Node implements Terrain {
 
     protected TerrainQuad findTopQuad() {
         boolean useFinder = false;
-        if (getParent() == null || !(getParent() instanceof TerrainQuad))
-             if (neighbourFinder == null)
+        if (getParent() == null || !(getParent() instanceof TerrainQuad)) {
+            if (neighbourFinder == null)
                 return null;
             else
                 useFinder = true;
+        }
 
-        TerrainQuad pQuad = (TerrainQuad) getParent();
+        TerrainQuad pQuad = null;
+        if (!useFinder)
+            pQuad = (TerrainQuad) getParent();
 
         if (quadrant == 2)
             return pQuad.getQuad(1);
         else if (quadrant == 4)
             return pQuad.getQuad(3);
         else if (quadrant == 1) {
-            TerrainQuad quad = null;
-            if (useFinder)
-                quad = neighbourFinder.getTopQuad(this);
-            else
-                quad = pQuad.findTopQuad();
+            TerrainQuad quad = pQuad.findTopQuad();
             if (quad != null)
                 return quad.getQuad(2);
         } else if (quadrant == 3) {
-            TerrainQuad quad = null;
-            if (useFinder)
-                quad = neighbourFinder.getTopQuad(this);
-            else
-                quad = pQuad.findTopQuad();
+            TerrainQuad quad = pQuad.findTopQuad();
             if (quad != null)
                 return quad.getQuad(4);
+        } else if (quadrant == 0) {
+            // at the top quad
+            if (useFinder) {
+                TerrainQuad quad = neighbourFinder.getTopQuad(this);
+                return quad;
+            }
         }
 
         return null;
@@ -1475,34 +1512,35 @@ public class TerrainQuad extends Node implements Terrain {
 
     protected TerrainQuad findLeftQuad() {
         boolean useFinder = false;
-        if (getParent() == null || !(getParent() instanceof TerrainQuad))
-             if (neighbourFinder == null)
+        if (getParent() == null || !(getParent() instanceof TerrainQuad)) {
+            if (neighbourFinder == null)
                 return null;
             else
                 useFinder = true;
+        }
 
-        TerrainQuad pQuad = (TerrainQuad) getParent();
+        TerrainQuad pQuad = null;
+        if (!useFinder)
+            pQuad = (TerrainQuad) getParent();
 
         if (quadrant == 3)
             return pQuad.getQuad(1);
         else if (quadrant == 4)
             return pQuad.getQuad(2);
         else if (quadrant == 1) {
-            TerrainQuad quad = null;
-            if (useFinder)
-                quad = neighbourFinder.getLeftQuad(this);
-            else
-                quad = pQuad.findLeftQuad();
+            TerrainQuad quad = pQuad.findLeftQuad();
             if (quad != null)
                 return quad.getQuad(3);
         } else if (quadrant == 2) {
-            TerrainQuad quad = null;
-            if (useFinder)
-                quad = neighbourFinder.getLeftQuad(this);
-            else
-                quad = pQuad.findLeftQuad();
+            TerrainQuad quad = pQuad.findLeftQuad();
             if (quad != null)
                 return quad.getQuad(4);
+        } else if (quadrant == 0) {
+            // at the top quad
+            if (useFinder) {
+                TerrainQuad quad = neighbourFinder.getLeftQuad(this);
+                return quad;
+            }
         }
 
         return null;

+ 19 - 20
engine/src/terrain/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java

@@ -49,21 +49,16 @@ public class UpdatedTerrainPatch {
     private int previousLod;
     private int rightLod,topLod,leftLod,bottomLod;
     private IntBuffer newIndexBuffer;
-    private boolean reIndexNeeded = false;
+    //private boolean reIndexNeeded = false;
     private boolean fixEdges = false;
 
-    public UpdatedTerrainPatch(TerrainPatch updatedPatch, int newLod) {
+    public UpdatedTerrainPatch(TerrainPatch updatedPatch) {
         this.updatedPatch = updatedPatch;
-        this.newLod = newLod;
     }
 
-    public UpdatedTerrainPatch(TerrainPatch updatedPatch, int newLod, int prevLOD, boolean reIndexNeeded) {
+    public UpdatedTerrainPatch(TerrainPatch updatedPatch, int newLod) {
         this.updatedPatch = updatedPatch;
         this.newLod = newLod;
-        this.previousLod = prevLOD;
-        this.reIndexNeeded = reIndexNeeded;
-        if (this.newLod <= 0)
-            throw new IllegalArgumentException();
     }
 
     public String getName() {
@@ -71,7 +66,7 @@ public class UpdatedTerrainPatch {
     }
 
     protected boolean lodChanged() {
-        if (reIndexNeeded && previousLod != newLod)
+        if ( previousLod != newLod)
             return true;
         else
             return false;
@@ -88,16 +83,16 @@ public class UpdatedTerrainPatch {
     protected int getNewLod() {
         return newLod;
     }
-
+    
     public void setNewLod(int newLod) {
         this.newLod = newLod;
         if (this.newLod < 0)
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("newLod cannot be less than zero, was: "+newLod);
     }
 
-    protected IntBuffer getNewIndexBuffer() {
+    /*protected IntBuffer getNewIndexBuffer() {
         return newIndexBuffer;
-    }
+    }*/
 
     protected void setNewIndexBuffer(IntBuffer newIndexBuffer) {
         this.newIndexBuffer = newIndexBuffer;
@@ -144,12 +139,16 @@ public class UpdatedTerrainPatch {
     }
 
     public boolean isReIndexNeeded() {
-        return reIndexNeeded;
+        if (lodChanged() || isFixEdges())
+            return true;
+        //if (leftLod != newLod || rightLod != newLod || bottomLod != newLod || topLod != newLod)
+        //    return true;
+        return false;
     }
 
-    public void setReIndexNeeded(boolean reIndexNeeded) {
+    /*public void setReIndexNeeded(boolean reIndexNeeded) {
         this.reIndexNeeded = reIndexNeeded;
-    }
+    }*/
 
     public boolean isFixEdges() {
         return fixEdges;
@@ -159,9 +158,9 @@ public class UpdatedTerrainPatch {
         this.fixEdges = fixEdges;
     }
 
-    public int getPreviousLod() {
+    /*public int getPreviousLod() {
         return previousLod;
-    }
+    }*/
 
     public void setPreviousLod(int previousLod) {
         this.previousLod = previousLod;
@@ -173,11 +172,11 @@ public class UpdatedTerrainPatch {
         updatedPatch.setLodTop(topLod);
         updatedPatch.setLodLeft(leftLod);
         updatedPatch.setLodBottom(bottomLod);
-        if (newIndexBuffer != null && (reIndexNeeded || fixEdges)) {
+        if (newIndexBuffer != null && isReIndexNeeded()) {
             updatedPatch.setPreviousLod(previousLod);
             updatedPatch.getMesh().clearBuffer(Type.Index);
             updatedPatch.getMesh().setBuffer(Type.Index, 3, newIndexBuffer);
         }
     }
-
+    
 }

+ 6 - 6
engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/DistanceLodCalculator.java

@@ -71,12 +71,12 @@ public class DistanceLodCalculator implements LodCalculator {
             int prevLOD = terrainPatch.getLod();
             UpdatedTerrainPatch utp = updates.get(terrainPatch.getName());
             if (utp == null) {
-                utp = new UpdatedTerrainPatch(terrainPatch, 0);
+                utp = new UpdatedTerrainPatch(terrainPatch);
                 updates.put(utp.getName(), utp);
             }
             utp.setNewLod(0);
             utp.setPreviousLod(prevLOD);
-            utp.setReIndexNeeded(true);
+            //utp.setReIndexNeeded(true);
             return true;
         }
         
@@ -89,15 +89,15 @@ public class DistanceLodCalculator implements LodCalculator {
                     //System.out.println("lod change: "+lod+" > "+i+"    dist: "+distance);
                 }
                 int prevLOD = terrainPatch.getLod();
-                //previousLod = lod;
-                //lod = i;
+                
                 UpdatedTerrainPatch utp = updates.get(terrainPatch.getName());
                 if (utp == null) {
-                    utp = new UpdatedTerrainPatch(terrainPatch, i);//save in here, do not update actual variables
+                    utp = new UpdatedTerrainPatch(terrainPatch);//save in here, do not update actual variables
                     updates.put(utp.getName(), utp);
                 }
+                utp.setNewLod(i);
                 utp.setPreviousLod(prevLOD);
-                utp.setReIndexNeeded(reIndexNeeded);
+                //utp.setReIndexNeeded(reIndexNeeded);
 
                 return reIndexNeeded;
             }

+ 4 - 4
engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java

@@ -106,15 +106,15 @@ public class PerspectiveLodCalculator implements LodCalculator {
                 }
                 int prevLOD = patch.getLod();
 
-                //previousLod = lod;
-                //lod = i;
+                
                 UpdatedTerrainPatch utp = updates.get(patch.getName());
                 if (utp == null) {
-                    utp = new UpdatedTerrainPatch(patch, i);//save in here, do not update actual variables
+                    utp = new UpdatedTerrainPatch(patch);//save in here, do not update actual variables
                     updates.put(utp.getName(), utp);
                 }
+                utp.setNewLod(i);
                 utp.setPreviousLod(prevLOD);
-                utp.setReIndexNeeded(reIndexNeeded);
+                //utp.setReIndexNeeded(reIndexNeeded);
                 return reIndexNeeded;
             }
         }

+ 54 - 19
engine/src/test/jme3test/terrain/TerrainTestTile.java

@@ -15,11 +15,13 @@ import com.jme3.material.Material;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector2f;
 import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
 import com.jme3.scene.Node;
+import com.jme3.scene.shape.Sphere;
 import com.jme3.terrain.ProgressMonitor;
 import com.jme3.terrain.Terrain;
+import com.jme3.terrain.geomipmap.MultiTerrainLodControl;
 import com.jme3.terrain.geomipmap.NeighbourFinder;
-import com.jme3.terrain.geomipmap.TerrainLodControl;
 import com.jme3.terrain.geomipmap.TerrainQuad;
 import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
 import com.jme3.texture.Texture;
@@ -29,7 +31,8 @@ import java.util.List;
 /**
  * Demonstrates the NeighbourFinder interface for TerrainQuads,
  * allowing you to tile terrains together without having to use
- * TerrainGrid.
+ * TerrainGrid. It also introduces the MultiTerrainLodControl that
+ * will seam the edges of all the terrains supplied.
  * 
  * @author sploreg
  */
@@ -75,7 +78,31 @@ public class TerrainTestTile extends SimpleApplication {
         rootNode.addLight(ambLight);
 
         cam.setLocation(new Vector3f(0, 256, 0));
-        cam.lookAtDirection(new Vector3f(0, -1f, 0).normalizeLocal(), Vector3f.UNIT_X);
+        cam.lookAtDirection(new Vector3f(0, -1, -1).normalizeLocal(), Vector3f.UNIT_Y);
+        
+        
+        Sphere s = new Sphere(12, 12, 3);
+        Geometry g = new Geometry("marker");
+        g.setMesh(s);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Red);
+        g.setMaterial(mat);
+        g.setLocalTranslation(0, -100, 0);
+        rootNode.attachChild(g);
+        
+        Geometry g2 = new Geometry("marker");
+        g2.setMesh(s);
+        mat.setColor("Color", ColorRGBA.Red);
+        g2.setMaterial(mat);
+        g2.setLocalTranslation(10, -100, 0);
+        rootNode.attachChild(g2);
+        
+        Geometry g3 = new Geometry("marker");
+        g3.setMesh(s);
+        mat.setColor("Color", ColorRGBA.Red);
+        g3.setMaterial(mat);
+        g3.setLocalTranslation(0, -100, 10);
+        rootNode.attachChild(g3);
     }
     
     public void loadHintText() {
@@ -110,6 +137,8 @@ public class TerrainTestTile extends SimpleApplication {
      * the use of NeighbourFinder.
      * It just links up the left,right,top,bottom TerrainQuads
      * so LOD can work.
+     * It does not implement many of the Terrain interface's methods,
+     * you will want to do that for your own implementations.
      */
     private class TiledTerrain extends Node implements Terrain, NeighbourFinder {
 
@@ -132,41 +161,43 @@ public class TerrainTestTile extends SimpleApplication {
             matTerrain.setFloat("DiffuseMap_0_scale", grassScale);
 
             // CREATE THE TERRAIN
-            terrain1 = new TerrainQuad("terrain", 65, 513, null);
-            TerrainLodControl control1 = new TerrainLodControl(terrain1, getCamera());
-            control1.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
-            terrain1.addControl(control1);
+            terrain1 = new TerrainQuad("terrain 1", 65, 513, null);
             terrain1.setMaterial(matTerrain);
             terrain1.setLocalTranslation(-256, -100, -256);
             terrain1.setLocalScale(1f, 1f, 1f);
             this.attachChild(terrain1);
 
-            terrain2 = new TerrainQuad("terrain", 65, 513, null);
-            TerrainLodControl control2 = new TerrainLodControl(terrain2, getCamera());
-            control2.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
-            terrain2.addControl(control2);
+            terrain2 = new TerrainQuad("terrain 2", 65, 513, null);
             terrain2.setMaterial(matTerrain);
             terrain2.setLocalTranslation(-256, -100, 256);
             terrain2.setLocalScale(1f, 1f, 1f);
             this.attachChild(terrain2);
 
-            terrain3 = new TerrainQuad("terrain", 65, 513, null);
-            TerrainLodControl control3 = new TerrainLodControl(terrain3, getCamera());
-            control3.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
-            terrain3.addControl(control3);
+            terrain3 = new TerrainQuad("terrain 3", 65, 513, null);
             terrain3.setMaterial(matTerrain);
             terrain3.setLocalTranslation(256, -100, -256);
             terrain3.setLocalScale(1f, 1f, 1f);
             this.attachChild(terrain3);
 
-            terrain4 = new TerrainQuad("terrain", 65, 513, null);
-            TerrainLodControl control4 = new TerrainLodControl(terrain4, getCamera());
-            control4.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
-            terrain4.addControl(control4);
+            terrain4 = new TerrainQuad("terrain 4", 65, 513, null);
             terrain4.setMaterial(matTerrain);
             terrain4.setLocalTranslation(256, -100, 256);
             terrain4.setLocalScale(1f, 1f, 1f);
             this.attachChild(terrain4);
+            
+            terrain1.setNeighbourFinder(this);
+            terrain2.setNeighbourFinder(this);
+            terrain3.setNeighbourFinder(this);
+            terrain4.setNeighbourFinder(this);
+            
+            MultiTerrainLodControl lodControl = new MultiTerrainLodControl(getCamera());
+            lodControl.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
+            lodControl.addTerrain(terrain1);
+            lodControl.addTerrain(terrain2);
+            lodControl.addTerrain(terrain3);// order of these seems to matter
+            lodControl.addTerrain(terrain4);
+            this.addControl(lodControl);
+            
         }
         
         /**
@@ -174,6 +205,7 @@ public class TerrainTestTile extends SimpleApplication {
          * 2  4
          */
         public TerrainQuad getRightQuad(TerrainQuad center) {
+            //System.out.println("lookup neighbour");
             if (center == terrain1)
                 return terrain3;
             if (center == terrain2)
@@ -187,6 +219,7 @@ public class TerrainTestTile extends SimpleApplication {
          * 2  4
          */
         public TerrainQuad getLeftQuad(TerrainQuad center) {
+            //System.out.println("lookup neighbour");
             if (center == terrain3)
                 return terrain1;
             if (center == terrain4)
@@ -200,6 +233,7 @@ public class TerrainTestTile extends SimpleApplication {
          * 2  4
          */
         public TerrainQuad getTopQuad(TerrainQuad center) {
+            //System.out.println("lookup neighbour");
             if (center == terrain2)
                 return terrain1;
             if (center == terrain4)
@@ -213,6 +247,7 @@ public class TerrainTestTile extends SimpleApplication {
          * 2  4
          */
         public TerrainQuad getDownQuad(TerrainQuad center) {
+            //System.out.println("lookup neighbour");
             if (center == terrain1)
                 return terrain2;
             if (center == terrain3)