Home | History | Annotate | Download | only in geomipmap
      1 /*
      2  * Copyright (c) 2009-2012 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.jme3.terrain.geomipmap;
     34 
     35 import com.jme3.bounding.BoundingBox;
     36 import com.jme3.bounding.BoundingVolume;
     37 import com.jme3.collision.Collidable;
     38 import com.jme3.collision.CollisionResults;
     39 import com.jme3.export.InputCapsule;
     40 import com.jme3.export.JmeExporter;
     41 import com.jme3.export.JmeImporter;
     42 import com.jme3.export.OutputCapsule;
     43 import com.jme3.material.Material;
     44 import com.jme3.math.FastMath;
     45 import com.jme3.math.Ray;
     46 import com.jme3.math.Vector2f;
     47 import com.jme3.math.Vector3f;
     48 import com.jme3.scene.Geometry;
     49 import com.jme3.scene.Node;
     50 import com.jme3.scene.Spatial;
     51 import com.jme3.scene.debug.WireBox;
     52 import com.jme3.terrain.ProgressMonitor;
     53 import com.jme3.terrain.Terrain;
     54 import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
     55 import com.jme3.terrain.geomipmap.picking.BresenhamTerrainPicker;
     56 import com.jme3.terrain.geomipmap.picking.TerrainPickData;
     57 import com.jme3.terrain.geomipmap.picking.TerrainPicker;
     58 import com.jme3.util.TangentBinormalGenerator;
     59 import java.io.IOException;
     60 import java.util.ArrayList;
     61 import java.util.HashMap;
     62 import java.util.List;
     63 import java.util.Map;
     64 import java.util.concurrent.ExecutorService;
     65 import java.util.concurrent.Executors;
     66 import java.util.concurrent.ThreadFactory;
     67 import java.util.logging.Level;
     68 import java.util.logging.Logger;
     69 
     70 /**
     71  * A terrain quad is a node in the quad tree of the terrain system.
     72  * The root terrain quad will be the only one that receives the update() call every frame
     73  * and it will determine if there has been any LOD change.
     74  *
     75  * The leaves of the terrain quad tree are Terrain Patches. These have the real geometry mesh.
     76  *
     77  *
     78  * Heightmap coordinates start from the bottom left of the world and work towards the
     79  * top right.
     80  *
     81  *  +x
     82  *  ^
     83  *  | ......N = length of heightmap
     84  *  | :     :
     85  *  | :     :
     86  *  | 0.....:
     87  *  +---------> +z
     88  * (world coordinates)
     89  *
     90  * @author Brent Owens
     91  */
     92 public class TerrainQuad extends Node implements Terrain {
     93 
     94     protected Vector2f offset;
     95 
     96     protected int totalSize; // the size of this entire terrain tree (on one side)
     97 
     98     protected int size; // size of this quad, can be between totalSize and patchSize
     99 
    100     protected int patchSize; // size of the individual patches
    101 
    102     protected Vector3f stepScale;
    103 
    104     protected float offsetAmount;
    105 
    106     protected int quadrant = 0; // 1=upper left, 2=lower left, 3=upper right, 4=lower right
    107 
    108     //protected LodCalculatorFactory lodCalculatorFactory;
    109     //protected LodCalculator lodCalculator;
    110 
    111     protected List<Vector3f> lastCameraLocations; // used for LOD calc
    112     private boolean lodCalcRunning = false;
    113     private int lodOffCount = 0;
    114     private int maxLod = -1;
    115     private HashMap<String,UpdatedTerrainPatch> updatedPatches;
    116     private final Object updatePatchesLock = new Object();
    117     private BoundingBox affectedAreaBBox; // only set in the root quad
    118 
    119     private TerrainPicker picker;
    120     private Vector3f lastScale = Vector3f.UNIT_XYZ;
    121 
    122     protected ExecutorService executor;
    123 
    124     protected ExecutorService createExecutorService() {
    125         return Executors.newSingleThreadExecutor(new ThreadFactory() {
    126             public Thread newThread(Runnable r) {
    127                 Thread th = new Thread(r);
    128                 th.setName("jME Terrain Thread");
    129                 th.setDaemon(true);
    130                 return th;
    131             }
    132         });
    133     }
    134 
    135     public TerrainQuad() {
    136         super("Terrain");
    137     }
    138 
    139     /**
    140      *
    141      * @param name the name of the scene element. This is required for
    142      * identification and comparison purposes.
    143      * @param patchSize size of the individual patches
    144      * @param totalSize the size of this entire terrain tree (on one side)
    145      * @param heightMap The height map to generate the terrain from (a flat
    146      * height map will be generated if this is null)
    147      */
    148     public TerrainQuad(String name, int patchSize, int totalSize, float[] heightMap) {
    149         this(name, patchSize, totalSize, Vector3f.UNIT_XYZ, heightMap);
    150     }
    151 
    152     /**
    153      *
    154      * @param name the name of the scene element. This is required for
    155      * identification and comparison purposes.
    156      * @param patchSize size of the individual patches
    157      * @param quadSize
    158      * @param totalSize the size of this entire terrain tree (on one side)
    159      * @param heightMap The height map to generate the terrain from (a flat
    160      * height map will be generated if this is null)
    161      */
    162     public TerrainQuad(String name, int patchSize, int quadSize, int totalSize, float[] heightMap) {
    163         this(name, patchSize, totalSize, quadSize, Vector3f.UNIT_XYZ, heightMap);
    164     }
    165 
    166     /**
    167      *
    168      * @param name the name of the scene element. This is required for
    169      * identification and comparison purposes.
    170      * @param patchSize size of the individual patches
    171      * @param size size of this quad, can be between totalSize and patchSize
    172      * @param scale
    173      * @param heightMap The height map to generate the terrain from (a flat
    174      * height map will be generated if this is null)
    175      */
    176     public TerrainQuad(String name, int patchSize, int size, Vector3f scale, float[] heightMap) {
    177         this(name, patchSize, size, scale, heightMap, size, new Vector2f(), 0);
    178         affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2);
    179         fixNormalEdges(affectedAreaBBox);
    180         addControl(new NormalRecalcControl(this));
    181     }
    182 
    183     /**
    184      *
    185      * @param name the name of the scene element. This is required for
    186      * identification and comparison purposes.
    187      * @param patchSize size of the individual patches
    188      * @param totalSize the size of this entire terrain tree (on one side)
    189      * @param quadSize
    190      * @param scale
    191      * @param heightMap The height map to generate the terrain from (a flat
    192      * height map will be generated if this is null)
    193      */
    194     public TerrainQuad(String name, int patchSize, int totalSize, int quadSize, Vector3f scale, float[] heightMap) {
    195         this(name, patchSize, quadSize, scale, heightMap, totalSize, new Vector2f(), 0);
    196         affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2);
    197         fixNormalEdges(affectedAreaBBox);
    198         addControl(new NormalRecalcControl(this));
    199     }
    200 
    201     protected TerrainQuad(String name, int patchSize, int quadSize,
    202                             Vector3f scale, float[] heightMap, int totalSize,
    203                             Vector2f offset, float offsetAmount)
    204     {
    205         super(name);
    206 
    207         if (heightMap == null)
    208             heightMap = generateDefaultHeightMap(quadSize);
    209 
    210         if (!FastMath.isPowerOfTwo(quadSize - 1)) {
    211             throw new RuntimeException("size given: " + quadSize + "  Terrain quad sizes may only be (2^N + 1)");
    212         }
    213         if (FastMath.sqrt(heightMap.length) > quadSize) {
    214             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!");
    215         }
    216 
    217         this.offset = offset;
    218         this.offsetAmount = offsetAmount;
    219         this.totalSize = totalSize;
    220         this.size = quadSize;
    221         this.patchSize = patchSize;
    222         this.stepScale = scale;
    223         //this.lodCalculatorFactory = lodCalculatorFactory;
    224         //this.lodCalculator = lodCalculator;
    225         split(patchSize, heightMap);
    226     }
    227 
    228     /*public void setLodCalculatorFactory(LodCalculatorFactory lodCalculatorFactory) {
    229         if (children != null) {
    230             for (int i = children.size(); --i >= 0;) {
    231                 Spatial child = children.get(i);
    232                 if (child instanceof TerrainQuad) {
    233                     ((TerrainQuad) child).setLodCalculatorFactory(lodCalculatorFactory);
    234                 } else if (child instanceof TerrainPatch) {
    235                     ((TerrainPatch) child).setLodCalculator(lodCalculatorFactory.createCalculator((TerrainPatch) child));
    236                 }
    237             }
    238         }
    239     }*/
    240 
    241 
    242     /**
    243      * Create just a flat heightmap
    244      */
    245     private float[] generateDefaultHeightMap(int size) {
    246         float[] heightMap = new float[size*size];
    247         return heightMap;
    248     }
    249 
    250      /**
    251       * Call from the update() method of a terrain controller to update
    252       * the LOD values of each patch.
    253       * This will perform the geometry calculation in a background thread and
    254       * do the actual update on the opengl thread.
    255       */
    256     public void update(List<Vector3f> locations, LodCalculator lodCalculator) {
    257         updateLOD(locations, lodCalculator);
    258     }
    259 
    260     /**
    261      * update the normals if there were any height changes recently.
    262      * Should only be called on the root quad
    263      */
    264     protected void updateNormals() {
    265 
    266         if (needToRecalculateNormals()) {
    267             //TODO background-thread this if it ends up being expensive
    268             fixNormals(affectedAreaBBox); // the affected patches
    269             fixNormalEdges(affectedAreaBBox); // the edges between the patches
    270 
    271             setNormalRecalcNeeded(null); // set to false
    272         }
    273     }
    274 
    275     // do all of the LOD calculations
    276     protected void updateLOD(List<Vector3f> locations, LodCalculator lodCalculator) {
    277         // update any existing ones that need updating
    278         updateQuadLODs();
    279 
    280         if (lodCalculator.isLodOff()) {
    281             // we want to calculate the base lod at least once
    282             if (lodOffCount == 1)
    283                 return;
    284             else
    285                 lodOffCount++;
    286         } else
    287             lodOffCount = 0;
    288 
    289         if (lastCameraLocations != null) {
    290             if (lastCameraLocationsTheSame(locations) && !lodCalculator.isLodOff())
    291                 return; // don't update if in same spot
    292             else
    293                 lastCameraLocations = cloneVectorList(locations);
    294         }
    295         else {
    296             lastCameraLocations = cloneVectorList(locations);
    297             return;
    298         }
    299 
    300         if (isLodCalcRunning()) {
    301             return;
    302         }
    303 
    304         if (getParent() instanceof TerrainQuad) {
    305             return; // we just want the root quad to perform this.
    306         }
    307 
    308         if (executor == null)
    309             executor = createExecutorService();
    310 
    311         UpdateLOD updateLodThread = new UpdateLOD(locations, lodCalculator);
    312         executor.execute(updateLodThread);
    313     }
    314 
    315     private synchronized boolean isLodCalcRunning() {
    316         return lodCalcRunning;
    317     }
    318 
    319     private synchronized void setLodCalcRunning(boolean running) {
    320         lodCalcRunning = running;
    321     }
    322 
    323     private List<Vector3f> cloneVectorList(List<Vector3f> locations) {
    324         List<Vector3f> cloned = new ArrayList<Vector3f>();
    325         for(Vector3f l : locations)
    326             cloned.add(l.clone());
    327         return cloned;
    328     }
    329 
    330     private boolean lastCameraLocationsTheSame(List<Vector3f> locations) {
    331         boolean theSame = true;
    332         for (Vector3f l : locations) {
    333             for (Vector3f v : lastCameraLocations) {
    334                 if (!v.equals(l) ) {
    335                     theSame = false;
    336                     return false;
    337                 }
    338             }
    339         }
    340         return theSame;
    341     }
    342 
    343     private int collideWithRay(Ray ray, CollisionResults results) {
    344         if (picker == null)
    345             picker = new BresenhamTerrainPicker(this);
    346 
    347         Vector3f intersection = picker.getTerrainIntersection(ray, results);
    348         if (intersection != null)
    349             return 1;
    350         else
    351             return 0;
    352     }
    353 
    354     /**
    355      * Generate the entropy values for the terrain for the "perspective" LOD
    356      * calculator. This routine can take a long time to run!
    357      * @param progressMonitor optional
    358      */
    359     public void generateEntropy(ProgressMonitor progressMonitor) {
    360         // only check this on the root quad
    361         if (isRootQuad())
    362             if (progressMonitor != null) {
    363                 int numCalc = (totalSize-1)/(patchSize-1); // make it an even number
    364                 progressMonitor.setMonitorMax(numCalc*numCalc);
    365             }
    366 
    367         if (children != null) {
    368             for (int i = children.size(); --i >= 0;) {
    369                 Spatial child = children.get(i);
    370                 if (child instanceof TerrainQuad) {
    371                         ((TerrainQuad) child).generateEntropy(progressMonitor);
    372                 } else if (child instanceof TerrainPatch) {
    373                     ((TerrainPatch) child).generateLodEntropies();
    374                     if (progressMonitor != null)
    375                         progressMonitor.incrementProgress(1);
    376                 }
    377             }
    378         }
    379 
    380         // only do this on the root quad
    381         if (isRootQuad())
    382             if (progressMonitor != null)
    383                 progressMonitor.progressComplete();
    384     }
    385 
    386     protected boolean isRootQuad() {
    387         return (getParent() != null && !(getParent() instanceof TerrainQuad) );
    388     }
    389 
    390     public Material getMaterial() {
    391         return getMaterial(null);
    392     }
    393 
    394     public Material getMaterial(Vector3f worldLocation) {
    395         // get the material from one of the children. They all share the same material
    396         if (children != null) {
    397             for (int i = children.size(); --i >= 0;) {
    398                 Spatial child = children.get(i);
    399                 if (child instanceof TerrainQuad) {
    400                     return ((TerrainQuad)child).getMaterial(worldLocation);
    401                 } else if (child instanceof TerrainPatch) {
    402                     return ((TerrainPatch)child).getMaterial();
    403                 }
    404             }
    405         }
    406         return null;
    407     }
    408 
    409     //public float getTextureCoordinateScale() {
    410     //    return 1f/(float)totalSize;
    411     //}
    412     public int getNumMajorSubdivisions() {
    413         return 1;
    414     }
    415 
    416     /**
    417      * Calculates the LOD of all child terrain patches.
    418      */
    419     private class UpdateLOD implements Runnable {
    420         private List<Vector3f> camLocations;
    421         private LodCalculator lodCalculator;
    422 
    423         UpdateLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) {
    424             this.camLocations = camLocations;
    425             this.lodCalculator = lodCalculator;
    426         }
    427 
    428         public void run() {
    429             long start = System.currentTimeMillis();
    430             if (isLodCalcRunning()) {
    431                 //System.out.println("thread already running");
    432                 return;
    433             }
    434             //System.out.println("spawned thread "+toString());
    435             setLodCalcRunning(true);
    436 
    437             // go through each patch and calculate its LOD based on camera distance
    438             HashMap<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>();
    439             boolean lodChanged = calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here
    440 
    441             if (!lodChanged) {
    442                 // not worth updating anything else since no one's LOD changed
    443                 setLodCalcRunning(false);
    444                 return;
    445             }
    446             // then calculate its neighbour LOD values for seaming in the shader
    447             findNeighboursLod(updated);
    448 
    449             fixEdges(updated); // 'updated' can get added to here
    450 
    451             reIndexPages(updated, lodCalculator.usesVariableLod());
    452 
    453             setUpdateQuadLODs(updated); // set back to main ogl thread
    454 
    455             setLodCalcRunning(false);
    456             //double duration = (System.currentTimeMillis()-start);
    457             //System.out.println("terminated in "+duration);
    458         }
    459     }
    460 
    461     private void setUpdateQuadLODs(HashMap<String,UpdatedTerrainPatch> updated) {
    462         synchronized (updatePatchesLock) {
    463             updatedPatches = updated;
    464         }
    465     }
    466 
    467     /**
    468      * Back on the ogl thread: update the terrain patch geometries
    469      * @param updatedPatches to be updated
    470      */
    471     private void updateQuadLODs() {
    472         synchronized (updatePatchesLock) {
    473 
    474             if (updatedPatches == null || updatedPatches.isEmpty())
    475                 return;
    476 
    477             // do the actual geometry update here
    478             for (UpdatedTerrainPatch utp : updatedPatches.values()) {
    479                 utp.updateAll();
    480             }
    481 
    482             updatedPatches.clear();
    483         }
    484     }
    485 
    486     public boolean hasPatchesToUpdate() {
    487         return updatedPatches != null && !updatedPatches.isEmpty();
    488     }
    489 
    490     protected boolean calculateLod(List<Vector3f> location, HashMap<String,UpdatedTerrainPatch> updates, LodCalculator lodCalculator) {
    491 
    492         boolean lodChanged = false;
    493 
    494         if (children != null) {
    495             for (int i = children.size(); --i >= 0;) {
    496                 Spatial child = children.get(i);
    497                 if (child instanceof TerrainQuad) {
    498                     boolean b = ((TerrainQuad) child).calculateLod(location, updates, lodCalculator);
    499                     if (b)
    500                         lodChanged = true;
    501                 } else if (child instanceof TerrainPatch) {
    502                     boolean b = lodCalculator.calculateLod((TerrainPatch) child, location, updates);
    503                     if (b)
    504                         lodChanged = true;
    505                 }
    506             }
    507         }
    508 
    509         return lodChanged;
    510     }
    511 
    512     protected synchronized void findNeighboursLod(HashMap<String,UpdatedTerrainPatch> updated) {
    513         if (children != null) {
    514             for (int x = children.size(); --x >= 0;) {
    515                 Spatial child = children.get(x);
    516                 if (child instanceof TerrainQuad) {
    517                     ((TerrainQuad) child).findNeighboursLod(updated);
    518                 } else if (child instanceof TerrainPatch) {
    519 
    520                     TerrainPatch patch = (TerrainPatch) child;
    521                     if (!patch.searchedForNeighboursAlready) {
    522                         // set the references to the neighbours
    523                         patch.rightNeighbour = findRightPatch(patch);
    524                         patch.bottomNeighbour = findDownPatch(patch);
    525                         patch.leftNeighbour = findLeftPatch(patch);
    526                         patch.topNeighbour = findTopPatch(patch);
    527                         patch.searchedForNeighboursAlready = true;
    528                     }
    529                     TerrainPatch right = patch.rightNeighbour;
    530                     TerrainPatch down = patch.bottomNeighbour;
    531 
    532                     UpdatedTerrainPatch utp = updated.get(patch.getName());
    533                     if (utp == null) {
    534                         utp = new UpdatedTerrainPatch(patch, patch.lod);
    535                         updated.put(utp.getName(), utp);
    536                     }
    537 
    538                     if (right != null) {
    539                         UpdatedTerrainPatch utpR = updated.get(right.getName());
    540                         if (utpR == null) {
    541                             utpR = new UpdatedTerrainPatch(right, right.lod);
    542                             updated.put(utpR.getName(), utpR);
    543                         }
    544 
    545                         utp.setRightLod(utpR.getNewLod());
    546                         utpR.setLeftLod(utp.getNewLod());
    547                     }
    548                     if (down != null) {
    549                         UpdatedTerrainPatch utpD = updated.get(down.getName());
    550                         if (utpD == null) {
    551                             utpD = new UpdatedTerrainPatch(down, down.lod);
    552                             updated.put(utpD.getName(), utpD);
    553                         }
    554 
    555                         utp.setBottomLod(utpD.getNewLod());
    556                         utpD.setTopLod(utp.getNewLod());
    557                     }
    558 
    559                 }
    560             }
    561         }
    562     }
    563 
    564     /**
    565      * TerrainQuad caches neighbours for faster LOD checks.
    566      * Sometimes you might want to reset this cache (for instance in TerrainGrid)
    567      */
    568     protected void resetCachedNeighbours() {
    569         if (children != null) {
    570             for (int x = children.size(); --x >= 0;) {
    571                 Spatial child = children.get(x);
    572                 if (child instanceof TerrainQuad) {
    573                     ((TerrainQuad) child).resetCachedNeighbours();
    574                 } else if (child instanceof TerrainPatch) {
    575                     TerrainPatch patch = (TerrainPatch) child;
    576                     patch.searchedForNeighboursAlready = false;
    577                 }
    578             }
    579         }
    580     }
    581 
    582     /**
    583      * Find any neighbours that should have their edges seamed because another neighbour
    584      * changed its LOD to a greater value (less detailed)
    585      */
    586     protected synchronized void fixEdges(HashMap<String,UpdatedTerrainPatch> updated) {
    587         if (children != null) {
    588             for (int x = children.size(); --x >= 0;) {
    589                 Spatial child = children.get(x);
    590                 if (child instanceof TerrainQuad) {
    591                     ((TerrainQuad) child).fixEdges(updated);
    592                 } else if (child instanceof TerrainPatch) {
    593                     TerrainPatch patch = (TerrainPatch) child;
    594                     UpdatedTerrainPatch utp = updated.get(patch.getName());
    595 
    596                     if(utp != null && utp.lodChanged()) {
    597                         if (!patch.searchedForNeighboursAlready) {
    598                             // set the references to the neighbours
    599                             patch.rightNeighbour = findRightPatch(patch);
    600                             patch.bottomNeighbour = findDownPatch(patch);
    601                             patch.leftNeighbour = findLeftPatch(patch);
    602                             patch.topNeighbour = findTopPatch(patch);
    603                             patch.searchedForNeighboursAlready = true;
    604                         }
    605                         TerrainPatch right = patch.rightNeighbour;
    606                         TerrainPatch down = patch.bottomNeighbour;
    607                         TerrainPatch top = patch.topNeighbour;
    608                         TerrainPatch left = patch.leftNeighbour;
    609                         if (right != null) {
    610                             UpdatedTerrainPatch utpR = updated.get(right.getName());
    611                             if (utpR == null) {
    612                                 utpR = new UpdatedTerrainPatch(right, right.lod);
    613                                 updated.put(utpR.getName(), utpR);
    614                             }
    615                             utpR.setFixEdges(true);
    616                         }
    617                         if (down != null) {
    618                             UpdatedTerrainPatch utpD = updated.get(down.getName());
    619                             if (utpD == null) {
    620                                 utpD = new UpdatedTerrainPatch(down, down.lod);
    621                                 updated.put(utpD.getName(), utpD);
    622                             }
    623                             utpD.setFixEdges(true);
    624                         }
    625                         if (top != null){
    626                             UpdatedTerrainPatch utpT = updated.get(top.getName());
    627                             if (utpT == null) {
    628                                 utpT = new UpdatedTerrainPatch(top, top.lod);
    629                                 updated.put(utpT.getName(), utpT);
    630                             }
    631                             utpT.setFixEdges(true);
    632                         }
    633                         if (left != null){
    634                             UpdatedTerrainPatch utpL = updated.get(left.getName());
    635                             if (utpL == null) {
    636                                 utpL = new UpdatedTerrainPatch(left, left.lod);
    637                                 updated.put(utpL.getName(), utpL);
    638                             }
    639                             utpL.setFixEdges(true);
    640                         }
    641                     }
    642                 }
    643             }
    644         }
    645     }
    646 
    647     protected synchronized void reIndexPages(HashMap<String,UpdatedTerrainPatch> updated, boolean usesVariableLod) {
    648         if (children != null) {
    649             for (int i = children.size(); --i >= 0;) {
    650                 Spatial child = children.get(i);
    651                 if (child instanceof TerrainQuad) {
    652                     ((TerrainQuad) child).reIndexPages(updated, usesVariableLod);
    653                 } else if (child instanceof TerrainPatch) {
    654                     ((TerrainPatch) child).reIndexGeometry(updated, usesVariableLod);
    655                 }
    656             }
    657         }
    658     }
    659 
    660     /**
    661      * <code>split</code> divides the heightmap data for four children. The
    662      * children are either quads or patches. This is dependent on the size of the
    663      * children. If the child's size is less than or equal to the set block
    664      * size, then patches are created, otherwise, quads are created.
    665      *
    666      * @param blockSize
    667      *			the blocks size to test against.
    668      * @param heightMap
    669      *			the height data.
    670      */
    671     protected void split(int blockSize, float[] heightMap) {
    672         if ((size >> 1) + 1 <= blockSize) {
    673             createQuadPatch(heightMap);
    674         } else {
    675             createQuad(blockSize, heightMap);
    676         }
    677 
    678     }
    679 
    680     /**
    681      * Quadrants, world coordinates, and heightmap coordinates (Y-up):
    682      *
    683      *         -z
    684      *      -u |
    685      *    -v  1|3
    686      *  -x ----+---- x
    687      *        2|4 u
    688      *         | v
    689      *         z
    690      * <code>createQuad</code> generates four new quads from this quad.
    691      * The heightmap's top left (0,0) coordinate is at the bottom, -x,-z
    692      * coordinate of the terrain, so it grows in the positive x.z direction.
    693      */
    694     protected void createQuad(int blockSize, float[] heightMap) {
    695         // create 4 terrain quads
    696         int quarterSize = size >> 2;
    697 
    698         int split = (size + 1) >> 1;
    699 
    700         Vector2f tempOffset = new Vector2f();
    701         offsetAmount += quarterSize;
    702 
    703         //if (lodCalculator == null)
    704         //    lodCalculator = createDefaultLodCalculator(); // set a default one
    705 
    706         // 1 upper left of heightmap, upper left quad
    707         float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split);
    708 
    709         Vector3f origin1 = new Vector3f(-quarterSize * stepScale.x, 0,
    710                         -quarterSize * stepScale.z);
    711 
    712         tempOffset.x = offset.x;
    713         tempOffset.y = offset.y;
    714         tempOffset.x += origin1.x;
    715         tempOffset.y += origin1.z;
    716 
    717         TerrainQuad quad1 = new TerrainQuad(getName() + "Quad1", blockSize,
    718                         split, stepScale, heightBlock1, totalSize, tempOffset,
    719                         offsetAmount);
    720         quad1.setLocalTranslation(origin1);
    721         quad1.quadrant = 1;
    722         this.attachChild(quad1);
    723 
    724         // 2 lower left of heightmap, lower left quad
    725         float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1,
    726                         split);
    727 
    728         Vector3f origin2 = new Vector3f(-quarterSize * stepScale.x, 0,
    729                         quarterSize * stepScale.z);
    730 
    731         tempOffset = new Vector2f();
    732         tempOffset.x = offset.x;
    733         tempOffset.y = offset.y;
    734         tempOffset.x += origin2.x;
    735         tempOffset.y += origin2.z;
    736 
    737         TerrainQuad quad2 = new TerrainQuad(getName() + "Quad2", blockSize,
    738                         split, stepScale, heightBlock2, totalSize, tempOffset,
    739                         offsetAmount);
    740         quad2.setLocalTranslation(origin2);
    741         quad2.quadrant = 2;
    742         this.attachChild(quad2);
    743 
    744         // 3 upper right of heightmap, upper right quad
    745         float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0,
    746                         split);
    747 
    748         Vector3f origin3 = new Vector3f(quarterSize * stepScale.x, 0,
    749                         -quarterSize * stepScale.z);
    750 
    751         tempOffset = new Vector2f();
    752         tempOffset.x = offset.x;
    753         tempOffset.y = offset.y;
    754         tempOffset.x += origin3.x;
    755         tempOffset.y += origin3.z;
    756 
    757         TerrainQuad quad3 = new TerrainQuad(getName() + "Quad3", blockSize,
    758                         split, stepScale, heightBlock3, totalSize, tempOffset,
    759                         offsetAmount);
    760         quad3.setLocalTranslation(origin3);
    761         quad3.quadrant = 3;
    762         this.attachChild(quad3);
    763 
    764         // 4 lower right of heightmap, lower right quad
    765         float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1,
    766                         split - 1, split);
    767 
    768         Vector3f origin4 = new Vector3f(quarterSize * stepScale.x, 0,
    769                         quarterSize * stepScale.z);
    770 
    771         tempOffset = new Vector2f();
    772         tempOffset.x = offset.x;
    773         tempOffset.y = offset.y;
    774         tempOffset.x += origin4.x;
    775         tempOffset.y += origin4.z;
    776 
    777         TerrainQuad quad4 = new TerrainQuad(getName() + "Quad4", blockSize,
    778                         split, stepScale, heightBlock4, totalSize, tempOffset,
    779                         offsetAmount);
    780         quad4.setLocalTranslation(origin4);
    781         quad4.quadrant = 4;
    782         this.attachChild(quad4);
    783 
    784     }
    785 
    786     public void generateDebugTangents(Material mat) {
    787         for (int x = children.size(); --x >= 0;) {
    788             Spatial child = children.get(x);
    789             if (child instanceof TerrainQuad) {
    790                 ((TerrainQuad)child).generateDebugTangents(mat);
    791             } else if (child instanceof TerrainPatch) {
    792                 Geometry debug = new Geometry( "Debug " + name,
    793                     TangentBinormalGenerator.genTbnLines( ((TerrainPatch)child).getMesh(), 0.8f));
    794                 attachChild(debug);
    795                 debug.setLocalTranslation(child.getLocalTranslation());
    796                 debug.setCullHint(CullHint.Never);
    797                 debug.setMaterial(mat);
    798             }
    799         }
    800     }
    801 
    802     /**
    803      * <code>createQuadPatch</code> creates four child patches from this quad.
    804      */
    805     protected void createQuadPatch(float[] heightMap) {
    806         // create 4 terrain patches
    807         int quarterSize = size >> 2;
    808         int halfSize = size >> 1;
    809         int split = (size + 1) >> 1;
    810 
    811         //if (lodCalculator == null)
    812         //    lodCalculator = createDefaultLodCalculator(); // set a default one
    813 
    814         offsetAmount += quarterSize;
    815 
    816         // 1 lower left
    817         float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split);
    818 
    819         Vector3f origin1 = new Vector3f(-halfSize * stepScale.x, 0, -halfSize
    820                         * stepScale.z);
    821 
    822         Vector2f tempOffset1 = new Vector2f();
    823         tempOffset1.x = offset.x;
    824         tempOffset1.y = offset.y;
    825         tempOffset1.x += origin1.x / 2;
    826         tempOffset1.y += origin1.z / 2;
    827 
    828         TerrainPatch patch1 = new TerrainPatch(getName() + "Patch1", split,
    829                         stepScale, heightBlock1, origin1, totalSize, tempOffset1,
    830                         offsetAmount);
    831         patch1.setQuadrant((short) 1);
    832         this.attachChild(patch1);
    833         patch1.setModelBound(new BoundingBox());
    834         patch1.updateModelBound();
    835         //patch1.setLodCalculator(lodCalculator);
    836         //TangentBinormalGenerator.generate(patch1);
    837 
    838         // 2 upper left
    839         float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1,
    840                         split);
    841 
    842         Vector3f origin2 = new Vector3f(-halfSize * stepScale.x, 0, 0);
    843 
    844         Vector2f tempOffset2 = new Vector2f();
    845         tempOffset2.x = offset.x;
    846         tempOffset2.y = offset.y;
    847         tempOffset2.x += origin1.x / 2;
    848         tempOffset2.y += quarterSize * stepScale.z;
    849 
    850         TerrainPatch patch2 = new TerrainPatch(getName() + "Patch2", split,
    851                         stepScale, heightBlock2, origin2, totalSize, tempOffset2,
    852                         offsetAmount);
    853         patch2.setQuadrant((short) 2);
    854         this.attachChild(patch2);
    855         patch2.setModelBound(new BoundingBox());
    856         patch2.updateModelBound();
    857         //patch2.setLodCalculator(lodCalculator);
    858         //TangentBinormalGenerator.generate(patch2);
    859 
    860         // 3 lower right
    861         float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0,
    862                         split);
    863 
    864         Vector3f origin3 = new Vector3f(0, 0, -halfSize * stepScale.z);
    865 
    866         Vector2f tempOffset3 = new Vector2f();
    867         tempOffset3.x = offset.x;
    868         tempOffset3.y = offset.y;
    869         tempOffset3.x += quarterSize * stepScale.x;
    870         tempOffset3.y += origin3.z / 2;
    871 
    872         TerrainPatch patch3 = new TerrainPatch(getName() + "Patch3", split,
    873                         stepScale, heightBlock3, origin3, totalSize, tempOffset3,
    874                         offsetAmount);
    875         patch3.setQuadrant((short) 3);
    876         this.attachChild(patch3);
    877         patch3.setModelBound(new BoundingBox());
    878         patch3.updateModelBound();
    879         //patch3.setLodCalculator(lodCalculator);
    880         //TangentBinormalGenerator.generate(patch3);
    881 
    882         // 4 upper right
    883         float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1,
    884                         split - 1, split);
    885 
    886         Vector3f origin4 = new Vector3f(0, 0, 0);
    887 
    888         Vector2f tempOffset4 = new Vector2f();
    889         tempOffset4.x = offset.x;
    890         tempOffset4.y = offset.y;
    891         tempOffset4.x += quarterSize * stepScale.x;
    892         tempOffset4.y += quarterSize * stepScale.z;
    893 
    894         TerrainPatch patch4 = new TerrainPatch(getName() + "Patch4", split,
    895                         stepScale, heightBlock4, origin4, totalSize, tempOffset4,
    896                         offsetAmount);
    897         patch4.setQuadrant((short) 4);
    898         this.attachChild(patch4);
    899         patch4.setModelBound(new BoundingBox());
    900         patch4.updateModelBound();
    901         //patch4.setLodCalculator(lodCalculator);
    902         //TangentBinormalGenerator.generate(patch4);
    903     }
    904 
    905     public float[] createHeightSubBlock(float[] heightMap, int x,
    906                     int y, int side) {
    907         float[] rVal = new float[side * side];
    908         int bsize = (int) FastMath.sqrt(heightMap.length);
    909         int count = 0;
    910         for (int i = y; i < side + y; i++) {
    911             for (int j = x; j < side + x; j++) {
    912                 if (j < bsize && i < bsize)
    913                     rVal[count] = heightMap[j + (i * bsize)];
    914                 count++;
    915             }
    916         }
    917         return rVal;
    918     }
    919 
    920     /**
    921      * A handy method that will attach all bounding boxes of this terrain
    922      * to the node you supply.
    923      * Useful to visualize the bounding boxes when debugging.
    924      *
    925      * @param parent that will get the bounding box shapes of the terrain attached to
    926      */
    927     public void attachBoundChildren(Node parent) {
    928         for (int i = 0; i < this.getQuantity(); i++) {
    929             if (this.getChild(i) instanceof TerrainQuad) {
    930                 ((TerrainQuad) getChild(i)).attachBoundChildren(parent);
    931             } else if (this.getChild(i) instanceof TerrainPatch) {
    932                 BoundingVolume bv = getChild(i).getWorldBound();
    933                 if (bv instanceof BoundingBox) {
    934                     attachBoundingBox((BoundingBox)bv, parent);
    935                 }
    936             }
    937         }
    938         BoundingVolume bv = getWorldBound();
    939         if (bv instanceof BoundingBox) {
    940             attachBoundingBox((BoundingBox)bv, parent);
    941         }
    942     }
    943 
    944     /**
    945      * used by attachBoundChildren()
    946      */
    947     private void attachBoundingBox(BoundingBox bb, Node parent) {
    948         WireBox wb = new WireBox(bb.getXExtent(), bb.getYExtent(), bb.getZExtent());
    949         Geometry g = new Geometry();
    950         g.setMesh(wb);
    951         g.setLocalTranslation(bb.getCenter());
    952         parent.attachChild(g);
    953     }
    954 
    955     /**
    956      * Signal if the normal vectors for the terrain need to be recalculated.
    957      * Does this by looking at the affectedAreaBBox bounding box. If the bbox
    958      * exists already, then it will grow the box to fit the new changedPoint.
    959      * If the affectedAreaBBox is null, then it will create one of unit size.
    960      *
    961      * @param needToRecalculateNormals if null, will cause needToRecalculateNormals() to return false
    962      */
    963     protected void setNormalRecalcNeeded(Vector2f changedPoint) {
    964         if (changedPoint == null) { // set needToRecalculateNormals() to false
    965             affectedAreaBBox = null;
    966             return;
    967         }
    968 
    969         if (affectedAreaBBox == null) {
    970             affectedAreaBBox = new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 1f, Float.MAX_VALUE, 1f); // unit length
    971         } else {
    972             // adjust size of box to be larger
    973             affectedAreaBBox.mergeLocal(new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 1f, Float.MAX_VALUE, 1f));
    974         }
    975     }
    976 
    977     protected boolean needToRecalculateNormals() {
    978         if (affectedAreaBBox != null)
    979             return true;
    980         if (!lastScale.equals(getWorldScale())) {
    981             affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size, Float.MAX_VALUE, size);
    982             lastScale = getWorldScale();
    983             return true;
    984         }
    985         return false;
    986     }
    987 
    988     /**
    989      * This will cause all normals for this terrain quad to be recalculated
    990      */
    991     protected void setNeedToRecalculateNormals() {
    992         affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2);
    993     }
    994 
    995     public float getHeightmapHeight(Vector2f xz) {
    996         // offset
    997         int halfSize = totalSize / 2;
    998         int x = Math.round((xz.x / getWorldScale().x) + halfSize);
    999         int z = Math.round((xz.y / getWorldScale().z) + halfSize);
   1000 
   1001         return getHeightmapHeight(x, z);
   1002     }
   1003 
   1004     /**
   1005      * This will just get the heightmap value at the supplied point,
   1006      * not an interpolated (actual) height value.
   1007      */
   1008     protected float getHeightmapHeight(int x, int z) {
   1009         int quad = findQuadrant(x, z);
   1010         int split = (size + 1) >> 1;
   1011         if (children != null) {
   1012             for (int i = children.size(); --i >= 0;) {
   1013                 Spatial spat = children.get(i);
   1014                 int col = x;
   1015                 int row = z;
   1016                 boolean match = false;
   1017 
   1018                 // get the childs quadrant
   1019                 int childQuadrant = 0;
   1020                 if (spat instanceof TerrainQuad) {
   1021                     childQuadrant = ((TerrainQuad) spat).getQuadrant();
   1022                 } else if (spat instanceof TerrainPatch) {
   1023                     childQuadrant = ((TerrainPatch) spat).getQuadrant();
   1024                 }
   1025 
   1026                 if (childQuadrant == 1 && (quad & 1) != 0) {
   1027                     match = true;
   1028                 } else if (childQuadrant == 2 && (quad & 2) != 0) {
   1029                     row = z - split + 1;
   1030                     match = true;
   1031                 } else if (childQuadrant == 3 && (quad & 4) != 0) {
   1032                     col = x - split + 1;
   1033                     match = true;
   1034                 } else if (childQuadrant == 4 && (quad & 8) != 0) {
   1035                     col = x - split + 1;
   1036                     row = z - split + 1;
   1037                     match = true;
   1038                 }
   1039 
   1040                 if (match) {
   1041                     if (spat instanceof TerrainQuad) {
   1042                         return ((TerrainQuad) spat).getHeightmapHeight(col, row);
   1043                     } else if (spat instanceof TerrainPatch) {
   1044                         return ((TerrainPatch) spat).getHeightmapHeight(col, row);
   1045                     }
   1046                 }
   1047 
   1048             }
   1049         }
   1050         return Float.NaN;
   1051     }
   1052 
   1053     protected Vector3f getMeshNormal(int x, int z) {
   1054         int quad = findQuadrant(x, z);
   1055         int split = (size + 1) >> 1;
   1056         if (children != null) {
   1057             for (int i = children.size(); --i >= 0;) {
   1058                 Spatial spat = children.get(i);
   1059                 int col = x;
   1060                 int row = z;
   1061                 boolean match = false;
   1062 
   1063                 // get the childs quadrant
   1064                 int childQuadrant = 0;
   1065                 if (spat instanceof TerrainQuad) {
   1066                     childQuadrant = ((TerrainQuad) spat).getQuadrant();
   1067                 } else if (spat instanceof TerrainPatch) {
   1068                     childQuadrant = ((TerrainPatch) spat).getQuadrant();
   1069                 }
   1070 
   1071                 if (childQuadrant == 1 && (quad & 1) != 0) {
   1072                     match = true;
   1073                 } else if (childQuadrant == 2 && (quad & 2) != 0) {
   1074                     row = z - split + 1;
   1075                     match = true;
   1076                 } else if (childQuadrant == 3 && (quad & 4) != 0) {
   1077                     col = x - split + 1;
   1078                     match = true;
   1079                 } else if (childQuadrant == 4 && (quad & 8) != 0) {
   1080                     col = x - split + 1;
   1081                     row = z - split + 1;
   1082                     match = true;
   1083                 }
   1084 
   1085                 if (match) {
   1086                     if (spat instanceof TerrainQuad) {
   1087                         return ((TerrainQuad) spat).getMeshNormal(col, row);
   1088                     } else if (spat instanceof TerrainPatch) {
   1089                         return ((TerrainPatch) spat).getMeshNormal(col, row);
   1090                     }
   1091                 }
   1092 
   1093             }
   1094         }
   1095         return null;
   1096     }
   1097 
   1098     public float getHeight(Vector2f xz) {
   1099         // offset
   1100         float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)totalSize / 2f);
   1101         float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)totalSize / 2f);
   1102         float height = getHeight(x, z);
   1103         height *= getWorldScale().y;
   1104         return height;
   1105     }
   1106 
   1107     /*
   1108      * gets an interpolated value at the specified point
   1109      * @param x coordinate translated into actual (positive) terrain grid coordinates
   1110      * @param y coordinate translated into actual (positive) terrain grid coordinates
   1111      */
   1112     protected float getHeight(float x, float z) {
   1113         x-=0.5f;
   1114         z-=0.5f;
   1115         float col = FastMath.floor(x);
   1116         float row = FastMath.floor(z);
   1117         boolean onX = false;
   1118         if(1 - (x - col)-(z - row) < 0) // what triangle to interpolate on
   1119             onX = true;
   1120         // v1--v2  ^
   1121         // |  / |  |
   1122         // | /  |  |
   1123         // v3--v4  | Z
   1124         //         |
   1125         // <-------Y
   1126         //     X
   1127         float v1 = getHeightmapHeight((int) FastMath.ceil(x), (int) FastMath.ceil(z));
   1128         float v2 = getHeightmapHeight((int) FastMath.floor(x), (int) FastMath.ceil(z));
   1129         float v3 = getHeightmapHeight((int) FastMath.ceil(x), (int) FastMath.floor(z));
   1130         float v4 = getHeightmapHeight((int) FastMath.floor(x), (int) FastMath.floor(z));
   1131         if (onX) {
   1132             return ((x - col) + (z - row) - 1f)*v1 + (1f - (x - col))*v2 + (1f - (z - row))*v3;
   1133         } else {
   1134             return (1f - (x - col) - (z - row))*v4 + (z - row)*v2 + (x - col)*v3;
   1135         }
   1136     }
   1137 
   1138     public Vector3f getNormal(Vector2f xz) {
   1139         // offset
   1140         float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)totalSize / 2f);
   1141         float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)totalSize / 2f);
   1142         Vector3f normal = getNormal(x, z, xz);
   1143 
   1144         return normal;
   1145     }
   1146 
   1147     protected Vector3f getNormal(float x, float z, Vector2f xz) {
   1148         x-=0.5f;
   1149         z-=0.5f;
   1150         float col = FastMath.floor(x);
   1151         float row = FastMath.floor(z);
   1152         boolean onX = false;
   1153         if(1 - (x - col)-(z - row) < 0) // what triangle to interpolate on
   1154             onX = true;
   1155         // v1--v2  ^
   1156         // |  / |  |
   1157         // | /  |  |
   1158         // v3--v4  | Z
   1159         //         |
   1160         // <-------Y
   1161         //     X
   1162         Vector3f n1 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.ceil(z));
   1163         Vector3f n2 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.ceil(z));
   1164         Vector3f n3 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.floor(z));
   1165         Vector3f n4 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.floor(z));
   1166 
   1167         return n1.add(n2).add(n3).add(n4).normalize();
   1168     }
   1169 
   1170     public void setHeight(Vector2f xz, float height) {
   1171         List<Vector2f> coord = new ArrayList<Vector2f>();
   1172         coord.add(xz);
   1173         List<Float> h = new ArrayList<Float>();
   1174         h.add(height);
   1175 
   1176         setHeight(coord, h);
   1177     }
   1178 
   1179     public void adjustHeight(Vector2f xz, float delta) {
   1180         List<Vector2f> coord = new ArrayList<Vector2f>();
   1181         coord.add(xz);
   1182         List<Float> h = new ArrayList<Float>();
   1183         h.add(delta);
   1184 
   1185         adjustHeight(coord, h);
   1186     }
   1187 
   1188     public void setHeight(List<Vector2f> xz, List<Float> height) {
   1189         setHeight(xz, height, true);
   1190     }
   1191 
   1192     public void adjustHeight(List<Vector2f> xz, List<Float> height) {
   1193         setHeight(xz, height, false);
   1194     }
   1195 
   1196     protected void setHeight(List<Vector2f> xz, List<Float> height, boolean overrideHeight) {
   1197         if (xz.size() != height.size())
   1198             throw new IllegalArgumentException("Both lists must be the same length!");
   1199 
   1200         int halfSize = totalSize / 2;
   1201 
   1202         List<LocationHeight> locations = new ArrayList<LocationHeight>();
   1203 
   1204         // offset
   1205         for (int i=0; i<xz.size(); i++) {
   1206             int x = Math.round((xz.get(i).x / getWorldScale().x) + halfSize);
   1207             int z = Math.round((xz.get(i).y / getWorldScale().z) + halfSize);
   1208             locations.add(new LocationHeight(x,z,height.get(i)));
   1209         }
   1210 
   1211         setHeight(locations, overrideHeight); // adjust height of the actual mesh
   1212 
   1213         // signal that the normals need updating
   1214         for (int i=0; i<xz.size(); i++)
   1215             setNormalRecalcNeeded(xz.get(i) );
   1216     }
   1217 
   1218     protected class LocationHeight {
   1219         int x;
   1220         int z;
   1221         float h;
   1222 
   1223         LocationHeight(){}
   1224 
   1225         LocationHeight(int x, int z, float h){
   1226             this.x = x;
   1227             this.z = z;
   1228             this.h = h;
   1229         }
   1230     }
   1231 
   1232     protected void setHeight(List<LocationHeight> locations, boolean overrideHeight) {
   1233         if (children == null)
   1234             return;
   1235 
   1236         List<LocationHeight> quadLH1 = new ArrayList<LocationHeight>();
   1237         List<LocationHeight> quadLH2 = new ArrayList<LocationHeight>();
   1238         List<LocationHeight> quadLH3 = new ArrayList<LocationHeight>();
   1239         List<LocationHeight> quadLH4 = new ArrayList<LocationHeight>();
   1240         Spatial quad1 = null;
   1241         Spatial quad2 = null;
   1242         Spatial quad3 = null;
   1243         Spatial quad4 = null;
   1244 
   1245         // get the child quadrants
   1246         for (int i = children.size(); --i >= 0;) {
   1247             Spatial spat = children.get(i);
   1248             int childQuadrant = 0;
   1249             if (spat instanceof TerrainQuad) {
   1250                 childQuadrant = ((TerrainQuad) spat).getQuadrant();
   1251             } else if (spat instanceof TerrainPatch) {
   1252                 childQuadrant = ((TerrainPatch) spat).getQuadrant();
   1253             }
   1254 
   1255             if (childQuadrant == 1)
   1256                 quad1 = spat;
   1257             else if (childQuadrant == 2)
   1258                 quad2 = spat;
   1259             else if (childQuadrant == 3)
   1260                 quad3 = spat;
   1261             else if (childQuadrant == 4)
   1262                 quad4 = spat;
   1263         }
   1264 
   1265         int split = (size + 1) >> 1;
   1266 
   1267         // distribute each locationHeight into the quadrant it intersects
   1268         for (LocationHeight lh : locations) {
   1269             int quad = findQuadrant(lh.x, lh.z);
   1270 
   1271             int col = lh.x;
   1272             int row = lh.z;
   1273 
   1274             if ((quad & 1) != 0) {
   1275                 quadLH1.add(lh);
   1276             }
   1277             if ((quad & 2) != 0) {
   1278                 row = lh.z - split + 1;
   1279                 quadLH2.add(new LocationHeight(lh.x, row, lh.h));
   1280             }
   1281             if ((quad & 4) != 0) {
   1282                 col = lh.x - split + 1;
   1283                 quadLH3.add(new LocationHeight(col, lh.z, lh.h));
   1284             }
   1285             if ((quad & 8) != 0) {
   1286                 col = lh.x - split + 1;
   1287                 row = lh.z - split + 1;
   1288                 quadLH4.add(new LocationHeight(col, row, lh.h));
   1289             }
   1290         }
   1291 
   1292         // send the locations to the children
   1293         if (!quadLH1.isEmpty()) {
   1294             if (quad1 instanceof TerrainQuad)
   1295                 ((TerrainQuad)quad1).setHeight(quadLH1, overrideHeight);
   1296             else if(quad1 instanceof TerrainPatch)
   1297                 ((TerrainPatch)quad1).setHeight(quadLH1, overrideHeight);
   1298         }
   1299 
   1300         if (!quadLH2.isEmpty()) {
   1301             if (quad2 instanceof TerrainQuad)
   1302                 ((TerrainQuad)quad2).setHeight(quadLH2, overrideHeight);
   1303             else if(quad2 instanceof TerrainPatch)
   1304                 ((TerrainPatch)quad2).setHeight(quadLH2, overrideHeight);
   1305         }
   1306 
   1307         if (!quadLH3.isEmpty()) {
   1308             if (quad3 instanceof TerrainQuad)
   1309                 ((TerrainQuad)quad3).setHeight(quadLH3, overrideHeight);
   1310             else if(quad3 instanceof TerrainPatch)
   1311                 ((TerrainPatch)quad3).setHeight(quadLH3, overrideHeight);
   1312         }
   1313 
   1314         if (!quadLH4.isEmpty()) {
   1315             if (quad4 instanceof TerrainQuad)
   1316                 ((TerrainQuad)quad4).setHeight(quadLH4, overrideHeight);
   1317             else if(quad4 instanceof TerrainPatch)
   1318                 ((TerrainPatch)quad4).setHeight(quadLH4, overrideHeight);
   1319         }
   1320     }
   1321 
   1322     protected boolean isPointOnTerrain(int x, int z) {
   1323         return (x >= 0 && x <= totalSize && z >= 0 && z <= totalSize);
   1324     }
   1325 
   1326 
   1327     public int getTerrainSize() {
   1328         return totalSize;
   1329     }
   1330 
   1331 
   1332     // a position can be in multiple quadrants, so use a bit anded value.
   1333     private int findQuadrant(int x, int y) {
   1334         int split = (size + 1) >> 1;
   1335         int quads = 0;
   1336         if (x < split && y < split)
   1337             quads |= 1;
   1338         if (x < split && y >= split - 1)
   1339             quads |= 2;
   1340         if (x >= split - 1 && y < split)
   1341             quads |= 4;
   1342         if (x >= split - 1 && y >= split - 1)
   1343             quads |= 8;
   1344         return quads;
   1345     }
   1346 
   1347     /**
   1348      * lock or unlock the meshes of this terrain.
   1349      * Locked meshes are uneditable but have better performance.
   1350      * @param locked or unlocked
   1351      */
   1352     public void setLocked(boolean locked) {
   1353         for (int i = 0; i < this.getQuantity(); i++) {
   1354             if (this.getChild(i) instanceof TerrainQuad) {
   1355                 ((TerrainQuad) getChild(i)).setLocked(locked);
   1356             } else if (this.getChild(i) instanceof TerrainPatch) {
   1357                 if (locked)
   1358                     ((TerrainPatch) getChild(i)).lockMesh();
   1359                 else
   1360                     ((TerrainPatch) getChild(i)).unlockMesh();
   1361             }
   1362         }
   1363     }
   1364 
   1365 
   1366     public int getQuadrant() {
   1367         return quadrant;
   1368     }
   1369 
   1370     public void setQuadrant(short quadrant) {
   1371         this.quadrant = quadrant;
   1372     }
   1373 
   1374 
   1375     protected TerrainPatch getPatch(int quad) {
   1376         if (children != null)
   1377             for (int x = children.size(); --x >= 0;) {
   1378                 Spatial child = children.get(x);
   1379                 if (child instanceof TerrainPatch) {
   1380                     TerrainPatch tb = (TerrainPatch) child;
   1381                     if (tb.getQuadrant() == quad)
   1382                         return tb;
   1383                 }
   1384             }
   1385         return null;
   1386     }
   1387 
   1388     protected TerrainQuad getQuad(int quad) {
   1389         if (children != null)
   1390             for (int x = children.size(); --x >= 0;) {
   1391                 Spatial child = children.get(x);
   1392                 if (child instanceof TerrainQuad) {
   1393                     TerrainQuad tq = (TerrainQuad) child;
   1394                     if (tq.getQuadrant() == quad)
   1395                         return tq;
   1396                 }
   1397             }
   1398         return null;
   1399     }
   1400 
   1401     protected TerrainPatch findRightPatch(TerrainPatch tp) {
   1402         if (tp.getQuadrant() == 1)
   1403             return getPatch(3);
   1404         else if (tp.getQuadrant() == 2)
   1405             return getPatch(4);
   1406         else if (tp.getQuadrant() == 3) {
   1407             // find the patch to the right and ask it for child 1.
   1408             TerrainQuad quad = findRightQuad();
   1409             if (quad != null)
   1410                 return quad.getPatch(1);
   1411         } else if (tp.getQuadrant() == 4) {
   1412             // find the patch to the right and ask it for child 2.
   1413             TerrainQuad quad = findRightQuad();
   1414             if (quad != null)
   1415                 return quad.getPatch(2);
   1416         }
   1417 
   1418         return null;
   1419     }
   1420 
   1421     protected TerrainPatch findDownPatch(TerrainPatch tp) {
   1422         if (tp.getQuadrant() == 1)
   1423             return getPatch(2);
   1424         else if (tp.getQuadrant() == 3)
   1425             return getPatch(4);
   1426         else if (tp.getQuadrant() == 2) {
   1427             // find the patch below and ask it for child 1.
   1428             TerrainQuad quad = findDownQuad();
   1429             if (quad != null)
   1430                 return quad.getPatch(1);
   1431         } else if (tp.getQuadrant() == 4) {
   1432             TerrainQuad quad = findDownQuad();
   1433             if (quad != null)
   1434                 return quad.getPatch(3);
   1435         }
   1436 
   1437         return null;
   1438     }
   1439 
   1440 
   1441     protected TerrainPatch findTopPatch(TerrainPatch tp) {
   1442         if (tp.getQuadrant() == 2)
   1443             return getPatch(1);
   1444         else if (tp.getQuadrant() == 4)
   1445             return getPatch(3);
   1446         else if (tp.getQuadrant() == 1) {
   1447             // find the patch above and ask it for child 2.
   1448             TerrainQuad quad = findTopQuad();
   1449             if (quad != null)
   1450                 return quad.getPatch(2);
   1451         } else if (tp.getQuadrant() == 3) {
   1452             TerrainQuad quad = findTopQuad();
   1453             if (quad != null)
   1454                 return quad.getPatch(4);
   1455         }
   1456 
   1457         return null;
   1458     }
   1459 
   1460     protected TerrainPatch findLeftPatch(TerrainPatch tp) {
   1461         if (tp.getQuadrant() == 3)
   1462             return getPatch(1);
   1463         else if (tp.getQuadrant() == 4)
   1464             return getPatch(2);
   1465         else if (tp.getQuadrant() == 1) {
   1466             // find the patch above and ask it for child 2.
   1467             TerrainQuad quad = findLeftQuad();
   1468             if (quad != null)
   1469                 return quad.getPatch(3);
   1470         } else if (tp.getQuadrant() == 2) {
   1471             TerrainQuad quad = findLeftQuad();
   1472             if (quad != null)
   1473                 return quad.getPatch(4);
   1474         }
   1475 
   1476         return null;
   1477     }
   1478 
   1479     protected TerrainQuad findRightQuad() {
   1480         if (getParent() == null || !(getParent() instanceof TerrainQuad))
   1481             return null;
   1482 
   1483         TerrainQuad pQuad = (TerrainQuad) getParent();
   1484 
   1485         if (quadrant == 1)
   1486             return pQuad.getQuad(3);
   1487         else if (quadrant == 2)
   1488             return pQuad.getQuad(4);
   1489         else if (quadrant == 3) {
   1490             TerrainQuad quad = pQuad.findRightQuad();
   1491             if (quad != null)
   1492                 return quad.getQuad(1);
   1493         } else if (quadrant == 4) {
   1494             TerrainQuad quad = pQuad.findRightQuad();
   1495             if (quad != null)
   1496                 return quad.getQuad(2);
   1497         }
   1498 
   1499         return null;
   1500     }
   1501 
   1502     protected TerrainQuad findDownQuad() {
   1503         if (getParent() == null || !(getParent() instanceof TerrainQuad))
   1504             return null;
   1505 
   1506         TerrainQuad pQuad = (TerrainQuad) getParent();
   1507 
   1508         if (quadrant == 1)
   1509             return pQuad.getQuad(2);
   1510         else if (quadrant == 3)
   1511             return pQuad.getQuad(4);
   1512         else if (quadrant == 2) {
   1513             TerrainQuad quad = pQuad.findDownQuad();
   1514             if (quad != null)
   1515                 return quad.getQuad(1);
   1516         } else if (quadrant == 4) {
   1517             TerrainQuad quad = pQuad.findDownQuad();
   1518             if (quad != null)
   1519                 return quad.getQuad(3);
   1520         }
   1521 
   1522         return null;
   1523     }
   1524 
   1525     protected TerrainQuad findTopQuad() {
   1526         if (getParent() == null || !(getParent() instanceof TerrainQuad))
   1527             return null;
   1528 
   1529         TerrainQuad pQuad = (TerrainQuad) getParent();
   1530 
   1531         if (quadrant == 2)
   1532             return pQuad.getQuad(1);
   1533         else if (quadrant == 4)
   1534             return pQuad.getQuad(3);
   1535         else if (quadrant == 1) {
   1536             TerrainQuad quad = pQuad.findTopQuad();
   1537             if (quad != null)
   1538                 return quad.getQuad(2);
   1539         } else if (quadrant == 3) {
   1540             TerrainQuad quad = pQuad.findTopQuad();
   1541             if (quad != null)
   1542                 return quad.getQuad(4);
   1543         }
   1544 
   1545         return null;
   1546     }
   1547 
   1548     protected TerrainQuad findLeftQuad() {
   1549         if (getParent() == null || !(getParent() instanceof TerrainQuad))
   1550             return null;
   1551 
   1552         TerrainQuad pQuad = (TerrainQuad) getParent();
   1553 
   1554         if (quadrant == 3)
   1555             return pQuad.getQuad(1);
   1556         else if (quadrant == 4)
   1557             return pQuad.getQuad(2);
   1558         else if (quadrant == 1) {
   1559             TerrainQuad quad = pQuad.findLeftQuad();
   1560             if (quad != null)
   1561                 return quad.getQuad(3);
   1562         } else if (quadrant == 2) {
   1563             TerrainQuad quad = pQuad.findLeftQuad();
   1564             if (quad != null)
   1565                 return quad.getQuad(4);
   1566         }
   1567 
   1568         return null;
   1569     }
   1570 
   1571     /**
   1572      * Find what terrain patches need normal recalculations and update
   1573      * their normals;
   1574      */
   1575     protected void fixNormals(BoundingBox affectedArea) {
   1576         if (children == null)
   1577             return;
   1578 
   1579         // go through the children and see if they collide with the affectedAreaBBox
   1580         // if they do, then update their normals
   1581         for (int x = children.size(); --x >= 0;) {
   1582             Spatial child = children.get(x);
   1583             if (child instanceof TerrainQuad) {
   1584                 if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound()) )
   1585                     ((TerrainQuad) child).fixNormals(affectedArea);
   1586             } else if (child instanceof TerrainPatch) {
   1587                 if (affectedArea != null && affectedArea.intersects(((TerrainPatch) child).getWorldBound()) )
   1588                     ((TerrainPatch) child).updateNormals(); // recalculate the patch's normals
   1589             }
   1590         }
   1591     }
   1592 
   1593     /**
   1594      * fix the normals on the edge of the terrain patches.
   1595      */
   1596     protected void fixNormalEdges(BoundingBox affectedArea) {
   1597         if (children == null)
   1598             return;
   1599 
   1600         for (int x = children.size(); --x >= 0;) {
   1601             Spatial child = children.get(x);
   1602             if (child instanceof TerrainQuad) {
   1603                 if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound()) )
   1604                     ((TerrainQuad) child).fixNormalEdges(affectedArea);
   1605             } else if (child instanceof TerrainPatch) {
   1606                 if (affectedArea != null && !affectedArea.intersects(((TerrainPatch) child).getWorldBound()) ) // if doesn't intersect, continue
   1607                     continue;
   1608 
   1609                 TerrainPatch tp = (TerrainPatch) child;
   1610                 TerrainPatch right = findRightPatch(tp);
   1611                 TerrainPatch bottom = findDownPatch(tp);
   1612                 TerrainPatch top = findTopPatch(tp);
   1613                 TerrainPatch left = findLeftPatch(tp);
   1614                 TerrainPatch topLeft = null;
   1615                 if (top != null)
   1616                     topLeft = findLeftPatch(top);
   1617                 TerrainPatch bottomRight = null;
   1618                 if (right != null)
   1619                     bottomRight = findDownPatch(right);
   1620                 TerrainPatch topRight = null;
   1621                 if (top != null)
   1622                     topRight = findRightPatch(top);
   1623                 TerrainPatch bottomLeft = null;
   1624                 if (left != null)
   1625                     bottomLeft = findDownPatch(left);
   1626 
   1627                 tp.fixNormalEdges(right, bottom, top, left, bottomRight, bottomLeft, topRight, topLeft);
   1628 
   1629             }
   1630         } // for each child
   1631 
   1632     }
   1633 
   1634 
   1635 
   1636     @Override
   1637     public int collideWith(Collidable other, CollisionResults results){
   1638         int total = 0;
   1639 
   1640         if (other instanceof Ray)
   1641             return collideWithRay((Ray)other, results);
   1642 
   1643         // if it didn't collide with this bbox, return
   1644         if (other instanceof BoundingVolume)
   1645             if (!this.getWorldBound().intersects((BoundingVolume)other))
   1646                 return total;
   1647 
   1648         for (Spatial child : children){
   1649             total += child.collideWith(other, results);
   1650         }
   1651         return total;
   1652     }
   1653 
   1654     /**
   1655      * Gather the terrain patches that intersect the given ray (toTest).
   1656      * This only tests the bounding boxes
   1657      * @param toTest
   1658      * @param results
   1659      */
   1660     public void findPick(Ray toTest, List<TerrainPickData> results) {
   1661 
   1662         if (getWorldBound() != null) {
   1663             if (getWorldBound().intersects(toTest)) {
   1664                 // further checking needed.
   1665                 for (int i = 0; i < getQuantity(); i++) {
   1666                     if (children.get(i) instanceof TerrainPatch) {
   1667                         TerrainPatch tp = (TerrainPatch) children.get(i);
   1668                         tp.ensurePositiveVolumeBBox();
   1669                         if (tp.getWorldBound().intersects(toTest)) {
   1670                             CollisionResults cr = new CollisionResults();
   1671                             toTest.collideWith(tp.getWorldBound(), cr);
   1672                             if (cr != null && cr.getClosestCollision() != null) {
   1673                                 cr.getClosestCollision().getDistance();
   1674                                 results.add(new TerrainPickData(tp, cr.getClosestCollision()));
   1675                             }
   1676                         }
   1677                     }
   1678                     else if (children.get(i) instanceof TerrainQuad) {
   1679                         ((TerrainQuad) children.get(i)).findPick(toTest, results);
   1680                     }
   1681                 }
   1682             }
   1683         }
   1684     }
   1685 
   1686 
   1687     /**
   1688      * Retrieve all Terrain Patches from all children and store them
   1689      * in the 'holder' list
   1690      * @param holder must not be null, will be populated when returns
   1691      */
   1692     public void getAllTerrainPatches(List<TerrainPatch> holder) {
   1693         if (children != null) {
   1694             for (int i = children.size(); --i >= 0;) {
   1695                 Spatial child = children.get(i);
   1696                 if (child instanceof TerrainQuad) {
   1697                     ((TerrainQuad) child).getAllTerrainPatches(holder);
   1698                 } else if (child instanceof TerrainPatch) {
   1699                     holder.add((TerrainPatch)child);
   1700                 }
   1701             }
   1702         }
   1703     }
   1704 
   1705     public void getAllTerrainPatchesWithTranslation(Map<TerrainPatch,Vector3f> holder, Vector3f translation) {
   1706         if (children != null) {
   1707             for (int i = children.size(); --i >= 0;) {
   1708                 Spatial child = children.get(i);
   1709                 if (child instanceof TerrainQuad) {
   1710                     ((TerrainQuad) child).getAllTerrainPatchesWithTranslation(holder, translation.clone().add(child.getLocalTranslation()));
   1711                 } else if (child instanceof TerrainPatch) {
   1712                     //if (holder.size() < 4)
   1713                     holder.put((TerrainPatch)child, translation.clone().add(child.getLocalTranslation()));
   1714                 }
   1715             }
   1716         }
   1717     }
   1718 
   1719     @Override
   1720     public void read(JmeImporter e) throws IOException {
   1721         super.read(e);
   1722         InputCapsule c = e.getCapsule(this);
   1723         size = c.readInt("size", 0);
   1724         stepScale = (Vector3f) c.readSavable("stepScale", null);
   1725         offset = (Vector2f) c.readSavable("offset", new Vector2f(0,0));
   1726         offsetAmount = c.readFloat("offsetAmount", 0);
   1727         quadrant = c.readInt("quadrant", 0);
   1728         totalSize = c.readInt("totalSize", 0);
   1729         //lodCalculator = (LodCalculator) c.readSavable("lodCalculator", createDefaultLodCalculator());
   1730         //lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null);
   1731 
   1732         if ( !(getParent() instanceof TerrainQuad) ) {
   1733             BoundingBox all = new BoundingBox(getWorldTranslation(), totalSize, totalSize, totalSize);
   1734             affectedAreaBBox = all;
   1735             updateNormals();
   1736         }
   1737     }
   1738 
   1739     @Override
   1740     public void write(JmeExporter e) throws IOException {
   1741         super.write(e);
   1742         OutputCapsule c = e.getCapsule(this);
   1743         c.write(size, "size", 0);
   1744         c.write(totalSize, "totalSize", 0);
   1745         c.write(stepScale, "stepScale", null);
   1746         c.write(offset, "offset", new Vector2f(0,0));
   1747         c.write(offsetAmount, "offsetAmount", 0);
   1748         c.write(quadrant, "quadrant", 0);
   1749         //c.write(lodCalculatorFactory, "lodCalculatorFactory", null);
   1750         //c.write(lodCalculator, "lodCalculator", null);
   1751     }
   1752 
   1753     @Override
   1754     public TerrainQuad clone() {
   1755         return this.clone(true);
   1756     }
   1757 
   1758 	@Override
   1759     public TerrainQuad clone(boolean cloneMaterials) {
   1760         TerrainQuad quadClone = (TerrainQuad) super.clone(cloneMaterials);
   1761         quadClone.name = name.toString();
   1762         quadClone.size = size;
   1763         quadClone.totalSize = totalSize;
   1764         if (stepScale != null) {
   1765             quadClone.stepScale = stepScale.clone();
   1766         }
   1767         if (offset != null) {
   1768             quadClone.offset = offset.clone();
   1769         }
   1770         quadClone.offsetAmount = offsetAmount;
   1771         quadClone.quadrant = quadrant;
   1772         //quadClone.lodCalculatorFactory = lodCalculatorFactory.clone();
   1773         //quadClone.lodCalculator = lodCalculator.clone();
   1774 
   1775         TerrainLodControl lodControlCloned = this.getControl(TerrainLodControl.class);
   1776         TerrainLodControl lodControl = quadClone.getControl(TerrainLodControl.class);
   1777 
   1778         if (lodControlCloned != null && !(getParent() instanceof TerrainQuad)) {
   1779             //lodControlCloned.setLodCalculator(lodControl.getLodCalculator().clone());
   1780         }
   1781         NormalRecalcControl normalControl = getControl(NormalRecalcControl.class);
   1782         if (normalControl != null)
   1783             normalControl.setTerrain(this);
   1784 
   1785         return quadClone;
   1786     }
   1787 
   1788     public int getMaxLod() {
   1789         if (maxLod < 0)
   1790             maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide
   1791 
   1792         return maxLod;
   1793     }
   1794 
   1795     public int getPatchSize() {
   1796         return patchSize;
   1797     }
   1798 
   1799     public int getTotalSize() {
   1800         return totalSize;
   1801     }
   1802 
   1803     public float[] getHeightMap() {
   1804 
   1805         float[] hm = null;
   1806         int length = ((size-1)/2)+1;
   1807         int area = size*size;
   1808         hm = new float[area];
   1809 
   1810         if (getChildren() != null && !getChildren().isEmpty()) {
   1811             float[] ul=null, ur=null, bl=null, br=null;
   1812             // get the child heightmaps
   1813             if (getChild(0) instanceof TerrainPatch) {
   1814                 for (Spatial s : getChildren()) {
   1815                     if ( ((TerrainPatch)s).getQuadrant() == 1)
   1816                         ul = ((TerrainPatch)s).getHeightMap();
   1817                     else if(((TerrainPatch) s).getQuadrant() == 2)
   1818                         bl = ((TerrainPatch)s).getHeightMap();
   1819                     else if(((TerrainPatch) s).getQuadrant() == 3)
   1820                         ur = ((TerrainPatch)s).getHeightMap();
   1821                     else if(((TerrainPatch) s).getQuadrant() == 4)
   1822                         br = ((TerrainPatch)s).getHeightMap();
   1823                 }
   1824             }
   1825             else {
   1826                 ul = getQuad(1).getHeightMap();
   1827                 bl = getQuad(2).getHeightMap();
   1828                 ur = getQuad(3).getHeightMap();
   1829                 br = getQuad(4).getHeightMap();
   1830             }
   1831 
   1832             // combine them into a single heightmap
   1833 
   1834 
   1835             // first upper blocks
   1836             for (int y=0; y<length; y++) { // rows
   1837                 for (int x1=0; x1<length; x1++) {
   1838                     int row = y*size;
   1839                     hm[row+x1] = ul[y*length+x1];
   1840                 }
   1841                 for (int x2=1; x2<length; x2++) {
   1842                     int row = y*size + length;
   1843                     hm[row+x2-1] = ur[y*length + x2];
   1844                 }
   1845             }
   1846             // second lower blocks
   1847             int rowOffset = size*length;
   1848             for (int y=1; y<length; y++) { // rows
   1849                 for (int x1=0; x1<length; x1++) {
   1850                     int row = (y-1)*size;
   1851                     hm[rowOffset+row+x1] = bl[y*length+x1];
   1852                 }
   1853                 for (int x2=1; x2<length; x2++) {
   1854                     int row = (y-1)*size + length;
   1855                     hm[rowOffset+row+x2-1] = br[y*length + x2];
   1856                 }
   1857             }
   1858         }
   1859 
   1860         return hm;
   1861     }
   1862 }
   1863 
   1864