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
33package com.jme3.terrain.geomipmap;
34
35import com.jme3.bounding.BoundingBox;
36import com.jme3.bounding.BoundingVolume;
37import com.jme3.collision.Collidable;
38import com.jme3.collision.CollisionResults;
39import com.jme3.export.InputCapsule;
40import com.jme3.export.JmeExporter;
41import com.jme3.export.JmeImporter;
42import com.jme3.export.OutputCapsule;
43import com.jme3.material.Material;
44import com.jme3.math.FastMath;
45import com.jme3.math.Ray;
46import com.jme3.math.Vector2f;
47import com.jme3.math.Vector3f;
48import com.jme3.scene.Geometry;
49import com.jme3.scene.Node;
50import com.jme3.scene.Spatial;
51import com.jme3.scene.debug.WireBox;
52import com.jme3.terrain.ProgressMonitor;
53import com.jme3.terrain.Terrain;
54import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
55import com.jme3.terrain.geomipmap.picking.BresenhamTerrainPicker;
56import com.jme3.terrain.geomipmap.picking.TerrainPickData;
57import com.jme3.terrain.geomipmap.picking.TerrainPicker;
58import com.jme3.util.TangentBinormalGenerator;
59import java.io.IOException;
60import java.util.ArrayList;
61import java.util.HashMap;
62import java.util.List;
63import java.util.Map;
64import java.util.concurrent.ExecutorService;
65import java.util.concurrent.Executors;
66import java.util.concurrent.ThreadFactory;
67import java.util.logging.Level;
68import 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 */
92public 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