• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
createExecutorService()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