/*
 * Copyright (c) 2009-2012 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme3.terrain.geomipmap;

import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingVolume;
import com.jme3.collision.Collidable;
import com.jme3.collision.CollisionResults;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.WireBox;
import com.jme3.terrain.ProgressMonitor;
import com.jme3.terrain.Terrain;
import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
import com.jme3.terrain.geomipmap.picking.BresenhamTerrainPicker;
import com.jme3.terrain.geomipmap.picking.TerrainPickData;
import com.jme3.terrain.geomipmap.picking.TerrainPicker;
import com.jme3.util.TangentBinormalGenerator;
import com.jme3.util.clone.Cloner;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * <p>
 * TerrainQuad is a heightfield-based terrain system. Heightfield terrain is fast and can
 * render large areas, and allows for easy Level of Detail control. However it does not
 * permit caves easily.
 * TerrainQuad is a quad tree, meaning that the root quad has four children, and each of
 * those children have four children. All the way down until you reach the bottom, the actual
 * geometry, the TerrainPatches.
 * If you look at a TerrainQuad in wireframe mode with the TerrainLODControl attached, you will
 * see blocks that change their LOD level together; these are the TerrainPatches. The TerrainQuad
 * is just an organizational structure for the TerrainPatches so patches that are not in the
 * view frustum get culled quickly.
 * TerrainQuads size are a power of 2, plus 1. So 513x513, or 1025x1025 etc.
 * Each point in the terrain is one unit apart from its neighbour. So a 513x513 terrain
 * will be 513 units wide and 513 units long.
 * Patch size can be specified on the terrain. This sets how large each geometry (TerrainPatch)
 * is. It also must be a power of 2 plus 1 so the terrain can be subdivided equally.
 * </p>
 * <p>
 * The height of the terrain can be modified at runtime using setHeight()
 * </p>
 * <p>
 * A terrain quad is a node in the quad tree of the terrain system.
 * The root terrain quad will be the only one that receives the update() call every frame
 * and it will determine if there has been any LOD change.
 * </p><p>
 * The leaves of the terrain quad tree are Terrain Patches. These have the real geometry mesh.
 * </p><p>
 * Heightmap coordinates start from the bottom left of the world and work towards the
 * top right.
 * </p><pre>
 *  +x
 *  ^
 *  | ......N = length of heightmap
 *  | :     :
 *  | :     :
 *  | 0.....:
 *  +---------&gt; +z
 * (world coordinates)
 * </pre>
 * @author Brent Owens
 */
public class TerrainQuad extends Node implements Terrain {
    protected Vector2f offset;

    protected int totalSize; // the size of this entire terrain tree (on one side)

    protected int size; // size of this quad, can be between totalSize and patchSize

    protected int patchSize; // size of the individual patches

    protected Vector3f stepScale;

    protected float offsetAmount;

    protected int quadrant = 0; // 1=upper left, 2=lower left, 3=upper right, 4=lower right
    private int maxLod = -1;
    private BoundingBox affectedAreaBBox; // only set in the root quad

    private TerrainPicker picker;
    private Vector3f lastScale = Vector3f.UNIT_XYZ;

    protected NeighbourFinder neighbourFinder;

    public TerrainQuad() {
        super("Terrain");
    }

    /**
     * Creates a terrain with:
     * <ul>
     * <li>the total, real-world, size of the terrain</li>
     * <li>the patchSize, or the size of each geometry tile of the terrain</li>
     * <li>the heightmap that defines the height of the terrain</li>
     * </ul>
     * <p>
     * A TerrainQuad of totalSize 513x513 will be 513 units wide and 513 units long.
     * PatchSize is just used to subdivide the terrain into tiles that can be culled.
     * </p>
     * @param name the name of the scene element. This is required for
     * identification and comparison purposes.
     * @param patchSize size of the individual patches (geometry). Power of 2 plus 1,
     * must be smaller than totalSize. (eg. 33, 65...)
     * @param totalSize the size of this entire terrain (on one side). Power of 2 plus 1
     * (eg. 513, 1025, 2049...)
     * @param heightMap The height map to generate the terrain from (a flat
     * height map will be generated if this is null). The size of one side of the heightmap
     * must match the totalSize. So a 513x513 heightmap is needed for a terrain with totalSize of 513.
     */
    public TerrainQuad(String name, int patchSize, int totalSize, float[] heightMap) {
        this(name, patchSize, totalSize, Vector3f.UNIT_XYZ, heightMap);

        affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2);
        fixNormalEdges(affectedAreaBBox);
        addControl(new NormalRecalcControl(this));
    }

    /**
     *
     * @param name the name of the scene element. This is required for
     * identification and comparison purposes.
     * @param patchSize size of the individual patches
     * @param quadSize
     * @param totalSize the size of this entire terrain tree (on one side)
     * @param heightMap The height map to generate the terrain from (a flat
     * height map will be generated if this is null)
     */
    @Deprecated
    public TerrainQuad(String name, int patchSize, int quadSize, int totalSize, float[] heightMap) {
        this(name, patchSize, totalSize, quadSize, Vector3f.UNIT_XYZ, heightMap);
    }

    /**
     *
     * @param name the name of the scene element. This is required for
     * identification and comparison purposes.
     * @param patchSize size of the individual patches
     * @param size size of this quad, can be between totalSize and patchSize
     * @param scale
     * @param heightMap The height map to generate the terrain from (a flat
     * height map will be generated if this is null)
     */
    @Deprecated
    public TerrainQuad(String name, int patchSize, int size, Vector3f scale, float[] heightMap) {
        this(name, patchSize, size, scale, heightMap, size, new Vector2f(), 0);
        //affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2);
        //fixNormalEdges(affectedAreaBBox);
        //addControl(new NormalRecalcControl(this));
    }

    /**
     *
     * @param name the name of the scene element. This is required for
     * identification and comparison purposes.
     * @param patchSize size of the individual patches
     * @param totalSize the size of this entire terrain tree (on one side)
     * @param quadSize
     * @param scale
     * @param heightMap The height map to generate the terrain from (a flat
     * height map will be generated if this is null)
     */
    @Deprecated
    public TerrainQuad(String name, int patchSize, int totalSize, int quadSize, Vector3f scale, float[] heightMap) {
        this(name, patchSize, quadSize, scale, heightMap, totalSize, new Vector2f(), 0);
        //affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2);
        //fixNormalEdges(affectedAreaBBox);
        //addControl(new NormalRecalcControl(this));
    }

    protected TerrainQuad(String name, int patchSize, int quadSize,
                            Vector3f scale, float[] heightMap, int totalSize,
                            Vector2f offset, float offsetAmount)
    {
        super(name);

        if (heightMap == null)
            heightMap = generateDefaultHeightMap(quadSize);

        if (!FastMath.isPowerOfTwo(quadSize - 1)) {
            throw new RuntimeException("size given: " + quadSize + "  Terrain quad sizes may only be (2^N + 1)");
        }
        if (FastMath.sqrt(heightMap.length) > quadSize) {
            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!");
        }

        this.offset = offset;
        this.offsetAmount = offsetAmount;
        this.totalSize = totalSize;
        this.size = quadSize;
        this.patchSize = patchSize;
        this.stepScale = scale;
        split(patchSize, heightMap);
    }

    public void setNeighbourFinder(NeighbourFinder neighbourFinder) {
        this.neighbourFinder = neighbourFinder;
        resetCachedNeighbours();
    }

    /**
     * Forces the recalculation of all normals on the terrain.
     */
    public void recalculateAllNormals() {
        affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2);
    }

    /**
     * Create just a flat heightmap
     */
    private float[] generateDefaultHeightMap(int size) {
        float[] heightMap = new float[size*size];
        return heightMap;
    }

    /**
     * update the normals if there were any height changes recently.
     * Should only be called on the root quad
     */
    protected void updateNormals() {

        if (needToRecalculateNormals()) {
            //TODO background-thread this if it ends up being expensive
            fixNormals(affectedAreaBBox); // the affected patches
            fixNormalEdges(affectedAreaBBox); // the edges between the patches

            setNormalRecalcNeeded(null); // set to false
        }
    }

    /**
     * Caches the transforms (except rotation) so the LOD calculator,
     * which runs on a separate thread, can access them safely.
     */
    protected void cacheTerrainTransforms() {
        for (int i = children.size(); --i >= 0;) {
            Spatial child = children.get(i);
            if (child instanceof TerrainQuad) {
                ((TerrainQuad) child).cacheTerrainTransforms();
            } else if (child instanceof TerrainPatch) {
                ((TerrainPatch) child).cacheTerrainTransforms();
            }
        }
    }

    private int collideWithRay(Ray ray, CollisionResults results) {
        if (picker == null)
            picker = new BresenhamTerrainPicker(this);

        Vector3f intersection = picker.getTerrainIntersection(ray, results);
        if (intersection != null) {
            if (ray.getLimit() < Float.POSITIVE_INFINITY) {
                if (results.getClosestCollision().getDistance() <= ray.getLimit())
                    return 1; // in range
                else
                    return 0; // out of range
            } else
                return 1;
        } else
            return 0;
    }

    /**
     * Generate the entropy values for the terrain for the "perspective" LOD
     * calculator. This routine can take a long time to run!
     * @param progressMonitor optional
     */
    public void generateEntropy(ProgressMonitor progressMonitor) {
        // only check this on the root quad
        if (isRootQuad())
            if (progressMonitor != null) {
                int numCalc = (totalSize-1)/(patchSize-1); // make it an even number
                progressMonitor.setMonitorMax(numCalc*numCalc);
            }

        if (children != null) {
            for (int i = children.size(); --i >= 0;) {
                Spatial child = children.get(i);
                if (child instanceof TerrainQuad) {
                        ((TerrainQuad) child).generateEntropy(progressMonitor);
                } else if (child instanceof TerrainPatch) {
                    ((TerrainPatch) child).generateLodEntropies();
                    if (progressMonitor != null)
                        progressMonitor.incrementProgress(1);
                }
            }
        }

        // only do this on the root quad
        if (isRootQuad())
            if (progressMonitor != null)
                progressMonitor.progressComplete();
    }

    protected boolean isRootQuad() {
        return (getParent() != null && !(getParent() instanceof TerrainQuad) );
    }

    public Material getMaterial() {
        return getMaterial(null);
    }

    public Material getMaterial(Vector3f worldLocation) {
        // get the material from one of the children. They all share the same material
        if (children != null) {
            for (int i = children.size(); --i >= 0;) {
                Spatial child = children.get(i);
                if (child instanceof TerrainQuad) {
                    return ((TerrainQuad)child).getMaterial(worldLocation);
                } else if (child instanceof TerrainPatch) {
                    return ((TerrainPatch)child).getMaterial();
                }
            }
        }
        return null;
    }

    public int getNumMajorSubdivisions() {
        return 1;
    }


    protected boolean calculateLod(List<Vector3f> location, HashMap<String,UpdatedTerrainPatch> updates, LodCalculator lodCalculator) {

        boolean lodChanged = false;

        if (children != null) {
            for (int i = children.size(); --i >= 0;) {
                Spatial child = children.get(i);
                if (child instanceof TerrainQuad) {
                    boolean b = ((TerrainQuad) child).calculateLod(location, updates, lodCalculator);
                    if (b)
                        lodChanged = true;
                } else if (child instanceof TerrainPatch) {
                    boolean b = lodCalculator.calculateLod((TerrainPatch) child, location, updates);
                    if (b)
                        lodChanged = true;
                }
            }
        }

        return lodChanged;
    }

    protected synchronized void findNeighboursLod(HashMap<String,UpdatedTerrainPatch> updated) {
        if (children != null) {
            for (int x = children.size(); --x >= 0;) {
                Spatial child = children.get(x);
                if (child instanceof TerrainQuad) {
                    ((TerrainQuad) child).findNeighboursLod(updated);
                } else if (child instanceof TerrainPatch) {

                    TerrainPatch patch = (TerrainPatch) child;
                    if (!patch.searchedForNeighboursAlready) {
                        // set the references to the neighbours
                        patch.rightNeighbour = findRightPatch(patch);
                        patch.bottomNeighbour = findDownPatch(patch);
                        patch.leftNeighbour = findLeftPatch(patch);
                        patch.topNeighbour = findTopPatch(patch);
                        patch.searchedForNeighboursAlready = true;
                    }
                    TerrainPatch right = patch.rightNeighbour;
                    TerrainPatch down = patch.bottomNeighbour;
                    TerrainPatch left = patch.leftNeighbour;
                    TerrainPatch top = patch.topNeighbour;

                    UpdatedTerrainPatch utp = updated.get(patch.getName());
                    if (utp == null) {
                        utp = new UpdatedTerrainPatch(patch, patch.lod);
                        updated.put(utp.getName(), utp);
                    }

                    if (right != null) {
                        UpdatedTerrainPatch utpR = updated.get(right.getName());
                        if (utpR == null) {
                            utpR = new UpdatedTerrainPatch(right);
                            updated.put(utpR.getName(), utpR);
                            utpR.setNewLod(right.lod);
                        }
                        utp.setRightLod(utpR.getNewLod());
                        utpR.setLeftLod(utp.getNewLod());
                    }
                    if (down != null) {
                        UpdatedTerrainPatch utpD = updated.get(down.getName());
                        if (utpD == null) {
                            utpD = new UpdatedTerrainPatch(down);
                            updated.put(utpD.getName(), utpD);
                            utpD.setNewLod(down.lod);
                        }
                        utp.setBottomLod(utpD.getNewLod());
                        utpD.setTopLod(utp.getNewLod());
                    }

                    if (left != null) {
                        UpdatedTerrainPatch utpL = updated.get(left.getName());
                        if (utpL == null) {
                            utpL = new UpdatedTerrainPatch(left);
                            updated.put(utpL.getName(), utpL);
                            utpL.setNewLod(left.lod);
                        }
                        utp.setLeftLod(utpL.getNewLod());
                        utpL.setRightLod(utp.getNewLod());
                    }
                    if (top != null) {
                        UpdatedTerrainPatch utpT = updated.get(top.getName());
                        if (utpT == null) {
                            utpT = new UpdatedTerrainPatch(top);
                            updated.put(utpT.getName(), utpT);
                            utpT.setNewLod(top.lod);
                        }
                        utp.setTopLod(utpT.getNewLod());
                        utpT.setBottomLod(utp.getNewLod());
                    }
                }
            }
        }
    }

    /**
     * Reset the cached references of neighbours.
     * TerrainQuad caches neighbours for faster LOD checks.
     * Sometimes you might want to reset this cache (for instance in TerrainGrid)
     */
    public void resetCachedNeighbours() {
        if (children != null) {
            for (int x = children.size(); --x >= 0;) {
                Spatial child = children.get(x);
                if (child instanceof TerrainQuad) {
                    ((TerrainQuad) child).resetCachedNeighbours();
                } else if (child instanceof TerrainPatch) {
                    TerrainPatch patch = (TerrainPatch) child;
                    patch.searchedForNeighboursAlready = false;
                }
            }
        }
    }

    /**
     * Find any neighbours that should have their edges seamed because another neighbour
     * changed its LOD to a greater value (less detailed)
     */
    protected synchronized void fixEdges(HashMap<String,UpdatedTerrainPatch> updated) {
        if (children != null) {
            for (int x = children.size(); --x >= 0;) {
                Spatial child = children.get(x);
                if (child instanceof TerrainQuad) {
                    ((TerrainQuad) child).fixEdges(updated);
                } else if (child instanceof TerrainPatch) {
                    TerrainPatch patch = (TerrainPatch) child;
                    UpdatedTerrainPatch utp = updated.get(patch.getName());

                    if(utp != null && utp.lodChanged()) {
                        if (!patch.searchedForNeighboursAlready) {
                            // set the references to the neighbours
                            patch.rightNeighbour = findRightPatch(patch);
                            patch.bottomNeighbour = findDownPatch(patch);
                            patch.leftNeighbour = findLeftPatch(patch);
                            patch.topNeighbour = findTopPatch(patch);
                            patch.searchedForNeighboursAlready = true;
                        }
                        TerrainPatch right = patch.rightNeighbour;
                        TerrainPatch down = patch.bottomNeighbour;
                        TerrainPatch top = patch.topNeighbour;
                        TerrainPatch left = patch.leftNeighbour;
                        if (right != null) {
                            UpdatedTerrainPatch utpR = updated.get(right.getName());
                            if (utpR == null) {
                                utpR = new UpdatedTerrainPatch(right);
                                updated.put(utpR.getName(), utpR);
                                utpR.setNewLod(right.lod);
                            }
                            utpR.setLeftLod(utp.getNewLod());
                            utpR.setFixEdges(true);
                        }
                        if (down != null) {
                            UpdatedTerrainPatch utpD = updated.get(down.getName());
                            if (utpD == null) {
                                utpD = new UpdatedTerrainPatch(down);
                                updated.put(utpD.getName(), utpD);
                                utpD.setNewLod(down.lod);
                            }
                            utpD.setTopLod(utp.getNewLod());
                            utpD.setFixEdges(true);
                        }
                        if (top != null){
                            UpdatedTerrainPatch utpT = updated.get(top.getName());
                            if (utpT == null) {
                                utpT = new UpdatedTerrainPatch(top);
                                updated.put(utpT.getName(), utpT);
                                utpT.setNewLod(top.lod);
                            }
                            utpT.setBottomLod(utp.getNewLod());
                            utpT.setFixEdges(true);
                        }
                        if (left != null){
                            UpdatedTerrainPatch utpL = updated.get(left.getName());
                            if (utpL == null) {
                                utpL = new UpdatedTerrainPatch(left);
                                updated.put(utpL.getName(), utpL);
                                utpL.setNewLod(left.lod);
                            }
                            utpL.setRightLod(utp.getNewLod());
                            utpL.setFixEdges(true);
                        }
                    }
                }
            }
        }
    }

    protected synchronized void reIndexPages(HashMap<String,UpdatedTerrainPatch> updated, boolean usesVariableLod) {
        if (children != null) {
            for (int i = children.size(); --i >= 0;) {
                Spatial child = children.get(i);
                if (child instanceof TerrainQuad) {
                    ((TerrainQuad) child).reIndexPages(updated, usesVariableLod);
                } else if (child instanceof TerrainPatch) {
                    ((TerrainPatch) child).reIndexGeometry(updated, usesVariableLod);
                }
            }
        }
    }

    /**
     * <code>split</code> divides the heightmap data for four children. The
     * children are either quads or patches. This is dependent on the size of the
     * children. If the child's size is less than or equal to the set block
     * size, then patches are created, otherwise, quads are created.
     *
     * @param blockSize
     *			the blocks size to test against.
     * @param heightMap
     *			the height data.
     */
    protected void split(int blockSize, float[] heightMap) {
        if ((size >> 1) + 1 <= blockSize) {
            createQuadPatch(heightMap);
        } else {
            createQuad(blockSize, heightMap);
        }

    }

    /**
     * Quadrants, world coordinates, and heightmap coordinates (Y-up):
     *
     *         -z
     *      -u |
     *    -v  1|3
     *  -x ----+---- x
     *        2|4 u
     *         | v
     *         z
     * <code>createQuad</code> generates four new quads from this quad.
     * The heightmap's top left (0,0) coordinate is at the bottom, -x,-z
     * coordinate of the terrain, so it grows in the positive x.z direction.
     */
    protected void createQuad(int blockSize, float[] heightMap) {
        // create 4 terrain quads
        int quarterSize = size >> 2;

        int split = (size + 1) >> 1;

        Vector2f tempOffset = new Vector2f();
        offsetAmount += quarterSize;

        //if (lodCalculator == null)
        //    lodCalculator = createDefaultLodCalculator(); // set a default one

        // 1 upper left of heightmap, upper left quad
        float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split);

        Vector3f origin1 = new Vector3f(-quarterSize * stepScale.x, 0,
                        -quarterSize * stepScale.z);

        tempOffset.x = offset.x;
        tempOffset.y = offset.y;
        tempOffset.x += origin1.x;
        tempOffset.y += origin1.z;

        TerrainQuad quad1 = new TerrainQuad(getName() + "Quad1", blockSize,
                        split, stepScale, heightBlock1, totalSize, tempOffset,
                        offsetAmount);
        quad1.setLocalTranslation(origin1);
        quad1.quadrant = 1;
        this.attachChild(quad1);

        // 2 lower left of heightmap, lower left quad
        float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1,
                        split);

        Vector3f origin2 = new Vector3f(-quarterSize * stepScale.x, 0,
                        quarterSize * stepScale.z);

        tempOffset = new Vector2f();
        tempOffset.x = offset.x;
        tempOffset.y = offset.y;
        tempOffset.x += origin2.x;
        tempOffset.y += origin2.z;

        TerrainQuad quad2 = new TerrainQuad(getName() + "Quad2", blockSize,
                        split, stepScale, heightBlock2, totalSize, tempOffset,
                        offsetAmount);
        quad2.setLocalTranslation(origin2);
        quad2.quadrant = 2;
        this.attachChild(quad2);

        // 3 upper right of heightmap, upper right quad
        float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0,
                        split);

        Vector3f origin3 = new Vector3f(quarterSize * stepScale.x, 0,
                        -quarterSize * stepScale.z);

        tempOffset = new Vector2f();
        tempOffset.x = offset.x;
        tempOffset.y = offset.y;
        tempOffset.x += origin3.x;
        tempOffset.y += origin3.z;

        TerrainQuad quad3 = new TerrainQuad(getName() + "Quad3", blockSize,
                        split, stepScale, heightBlock3, totalSize, tempOffset,
                        offsetAmount);
        quad3.setLocalTranslation(origin3);
        quad3.quadrant = 3;
        this.attachChild(quad3);

        // 4 lower right of heightmap, lower right quad
        float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1,
                        split - 1, split);

        Vector3f origin4 = new Vector3f(quarterSize * stepScale.x, 0,
                        quarterSize * stepScale.z);

        tempOffset = new Vector2f();
        tempOffset.x = offset.x;
        tempOffset.y = offset.y;
        tempOffset.x += origin4.x;
        tempOffset.y += origin4.z;

        TerrainQuad quad4 = new TerrainQuad(getName() + "Quad4", blockSize,
                        split, stepScale, heightBlock4, totalSize, tempOffset,
                        offsetAmount);
        quad4.setLocalTranslation(origin4);
        quad4.quadrant = 4;
        this.attachChild(quad4);

    }

    public void generateDebugTangents(Material mat) {
        for (int x = children.size(); --x >= 0;) {
            Spatial child = children.get(x);
            if (child instanceof TerrainQuad) {
                ((TerrainQuad)child).generateDebugTangents(mat);
            } else if (child instanceof TerrainPatch) {
                Geometry debug = new Geometry( "Debug " + name,
                    TangentBinormalGenerator.genTbnLines( ((TerrainPatch)child).getMesh(), 0.8f));
                attachChild(debug);
                debug.setLocalTranslation(child.getLocalTranslation());
                debug.setCullHint(CullHint.Never);
                debug.setMaterial(mat);
            }
        }
    }

    /**
     * <code>createQuadPatch</code> creates four child patches from this quad.
     */
    protected void createQuadPatch(float[] heightMap) {
        // create 4 terrain patches
        int quarterSize = size >> 2;
        int halfSize = size >> 1;
        int split = (size + 1) >> 1;

        //if (lodCalculator == null)
        //    lodCalculator = createDefaultLodCalculator(); // set a default one

        offsetAmount += quarterSize;

        // 1 lower left
        float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split);

        Vector3f origin1 = new Vector3f(-halfSize * stepScale.x, 0, -halfSize
                        * stepScale.z);

        Vector2f tempOffset1 = new Vector2f();
        tempOffset1.x = offset.x;
        tempOffset1.y = offset.y;
        tempOffset1.x += origin1.x / 2;
        tempOffset1.y += origin1.z / 2;

        TerrainPatch patch1 = new TerrainPatch(getName() + "Patch1", split,
                        stepScale, heightBlock1, origin1, totalSize, tempOffset1,
                        offsetAmount);
        patch1.setQuadrant((short) 1);
        this.attachChild(patch1);
        patch1.setModelBound(new BoundingBox());
        patch1.updateModelBound();
        //patch1.setLodCalculator(lodCalculator);
        //TangentBinormalGenerator.generate(patch1);

        // 2 upper left
        float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1,
                        split);

        Vector3f origin2 = new Vector3f(-halfSize * stepScale.x, 0, 0);

        Vector2f tempOffset2 = new Vector2f();
        tempOffset2.x = offset.x;
        tempOffset2.y = offset.y;
        tempOffset2.x += origin1.x / 2;
        tempOffset2.y += quarterSize * stepScale.z;

        TerrainPatch patch2 = new TerrainPatch(getName() + "Patch2", split,
                        stepScale, heightBlock2, origin2, totalSize, tempOffset2,
                        offsetAmount);
        patch2.setQuadrant((short) 2);
        this.attachChild(patch2);
        patch2.setModelBound(new BoundingBox());
        patch2.updateModelBound();
        //patch2.setLodCalculator(lodCalculator);
        //TangentBinormalGenerator.generate(patch2);

        // 3 lower right
        float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0,
                        split);

        Vector3f origin3 = new Vector3f(0, 0, -halfSize * stepScale.z);

        Vector2f tempOffset3 = new Vector2f();
        tempOffset3.x = offset.x;
        tempOffset3.y = offset.y;
        tempOffset3.x += quarterSize * stepScale.x;
        tempOffset3.y += origin3.z / 2;

        TerrainPatch patch3 = new TerrainPatch(getName() + "Patch3", split,
                        stepScale, heightBlock3, origin3, totalSize, tempOffset3,
                        offsetAmount);
        patch3.setQuadrant((short) 3);
        this.attachChild(patch3);
        patch3.setModelBound(new BoundingBox());
        patch3.updateModelBound();
        //patch3.setLodCalculator(lodCalculator);
        //TangentBinormalGenerator.generate(patch3);

        // 4 upper right
        float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1,
                        split - 1, split);

        Vector3f origin4 = new Vector3f(0, 0, 0);

        Vector2f tempOffset4 = new Vector2f();
        tempOffset4.x = offset.x;
        tempOffset4.y = offset.y;
        tempOffset4.x += quarterSize * stepScale.x;
        tempOffset4.y += quarterSize * stepScale.z;

        TerrainPatch patch4 = new TerrainPatch(getName() + "Patch4", split,
                        stepScale, heightBlock4, origin4, totalSize, tempOffset4,
                        offsetAmount);
        patch4.setQuadrant((short) 4);
        this.attachChild(patch4);
        patch4.setModelBound(new BoundingBox());
        patch4.updateModelBound();
        //patch4.setLodCalculator(lodCalculator);
        //TangentBinormalGenerator.generate(patch4);
    }

    public float[] createHeightSubBlock(float[] heightMap, int x,
                    int y, int side) {
        float[] rVal = new float[side * side];
        int bsize = (int) FastMath.sqrt(heightMap.length);
        int count = 0;
        for (int i = y; i < side + y; i++) {
            for (int j = x; j < side + x; j++) {
                if (j < bsize && i < bsize)
                    rVal[count] = heightMap[j + (i * bsize)];
                count++;
            }
        }
        return rVal;
    }

    /**
     * A handy method that will attach all bounding boxes of this terrain
     * to the node you supply.
     * Useful to visualize the bounding boxes when debugging.
     *
     * @param parent that will get the bounding box shapes of the terrain attached to
     */
    public void attachBoundChildren(Node parent) {
        for (int i = 0; i < this.getQuantity(); i++) {
            if (this.getChild(i) instanceof TerrainQuad) {
                ((TerrainQuad) getChild(i)).attachBoundChildren(parent);
            } else if (this.getChild(i) instanceof TerrainPatch) {
                BoundingVolume bv = getChild(i).getWorldBound();
                if (bv instanceof BoundingBox) {
                    attachBoundingBox((BoundingBox)bv, parent);
                }
            }
        }
        BoundingVolume bv = getWorldBound();
        if (bv instanceof BoundingBox) {
            attachBoundingBox((BoundingBox)bv, parent);
        }
    }

    /**
     * used by attachBoundChildren()
     */
    private void attachBoundingBox(BoundingBox bb, Node parent) {
        WireBox wb = new WireBox(bb.getXExtent(), bb.getYExtent(), bb.getZExtent());
        Geometry g = new Geometry();
        g.setMesh(wb);
        g.setLocalTranslation(bb.getCenter());
        parent.attachChild(g);
    }

    /**
     * Signal if the normal vectors for the terrain need to be recalculated.
     * Does this by looking at the affectedAreaBBox bounding box. If the bbox
     * exists already, then it will grow the box to fit the new changedPoint.
     * If the affectedAreaBBox is null, then it will create one of unit size.
     *
     * @param needToRecalculateNormals if null, will cause needToRecalculateNormals() to return false
     */
    protected void setNormalRecalcNeeded(Vector2f changedPoint) {
        if (changedPoint == null) { // set needToRecalculateNormals() to false
            affectedAreaBBox = null;
            return;
        }

        if (affectedAreaBBox == null) {
            affectedAreaBBox = new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 1f, Float.MAX_VALUE, 1f); // unit length
        } else {
            // adjust size of box to be larger
            affectedAreaBBox.mergeLocal(new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 1f, Float.MAX_VALUE, 1f));
        }
    }

    protected boolean needToRecalculateNormals() {
        if (affectedAreaBBox != null)
            return true;
        if (!lastScale.equals(getWorldScale())) {
            affectedAreaBBox = new BoundingBox(getWorldTranslation(), Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);
            lastScale = getWorldScale();
            return true;
        }
        return false;
    }

    /**
     * This will cause all normals for this terrain quad to be recalculated
     */
    protected void setNeedToRecalculateNormals() {
        affectedAreaBBox = new BoundingBox(getWorldTranslation(), Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);
    }

    public float getHeightmapHeight(Vector2f xz) {
        // offset
        int halfSize = totalSize / 2;
        int x = Math.round((xz.x / getWorldScale().x) + halfSize);
        int z = Math.round((xz.y / getWorldScale().z) + halfSize);

        if (!isInside(x, z))
            return Float.NaN;
        return getHeightmapHeight(x, z);
    }

    /**
     * This will just get the heightmap value at the supplied point,
     * not an interpolated (actual) height value.
     */
    protected float getHeightmapHeight(int x, int z) {
        int quad = findQuadrant(x, z);
        int split = (size + 1) >> 1;
        if (children != null) {
            for (int i = children.size(); --i >= 0;) {
                Spatial spat = children.get(i);
                int col = x;
                int row = z;
                boolean match = false;

                // get the childs quadrant
                int childQuadrant = 0;
                if (spat instanceof TerrainQuad) {
                    childQuadrant = ((TerrainQuad) spat).getQuadrant();
                } else if (spat instanceof TerrainPatch) {
                    childQuadrant = ((TerrainPatch) spat).getQuadrant();
                }

                if (childQuadrant == 1 && (quad & 1) != 0) {
                    match = true;
                } else if (childQuadrant == 2 && (quad & 2) != 0) {
                    row = z - split + 1;
                    match = true;
                } else if (childQuadrant == 3 && (quad & 4) != 0) {
                    col = x - split + 1;
                    match = true;
                } else if (childQuadrant == 4 && (quad & 8) != 0) {
                    col = x - split + 1;
                    row = z - split + 1;
                    match = true;
                }

                if (match) {
                    if (spat instanceof TerrainQuad) {
                        return ((TerrainQuad) spat).getHeightmapHeight(col, row);
                    } else if (spat instanceof TerrainPatch) {
                        return ((TerrainPatch) spat).getHeightmapHeight(col, row);
                    }
                }

            }
        }
        return Float.NaN;
    }

    protected Vector3f getMeshNormal(int x, int z) {
        int quad = findQuadrant(x, z);
        int split = (size + 1) >> 1;
        if (children != null) {
            for (int i = children.size(); --i >= 0;) {
                Spatial spat = children.get(i);
                int col = x;
                int row = z;
                boolean match = false;

                // get the childs quadrant
                int childQuadrant = 0;
                if (spat instanceof TerrainQuad) {
                    childQuadrant = ((TerrainQuad) spat).getQuadrant();
                } else if (spat instanceof TerrainPatch) {
                    childQuadrant = ((TerrainPatch) spat).getQuadrant();
                }

                if (childQuadrant == 1 && (quad & 1) != 0) {
                    match = true;
                } else if (childQuadrant == 2 && (quad & 2) != 0) {
                    row = z - split + 1;
                    match = true;
                } else if (childQuadrant == 3 && (quad & 4) != 0) {
                    col = x - split + 1;
                    match = true;
                } else if (childQuadrant == 4 && (quad & 8) != 0) {
                    col = x - split + 1;
                    row = z - split + 1;
                    match = true;
                }

                if (match) {
                    if (spat instanceof TerrainQuad) {
                        return ((TerrainQuad) spat).getMeshNormal(col, row);
                    } else if (spat instanceof TerrainPatch) {
                        return ((TerrainPatch) spat).getMeshNormal(col, row);
                    }
                }

            }
        }
        return null;
    }

    /**
     * is the 2d point inside the terrain?
     * @param x local coordinate
     * @param z local coordinate
     */
    private boolean isInside(int x, int z) {
        if (x < 0 || z < 0 || x > totalSize || z > totalSize)
            return false;
        return true;
    }

    /**
     * Used for searching for a child and keeping
     * track of its quadrant
     */
    private class QuadrantChild {
        int col;
        int row;
        Spatial child;

        QuadrantChild(int col, int row, Spatial child) {
            this.col = col;
            this.row = row;
            this.child = child;
        }
    }

    private QuadrantChild findMatchingChild(int x, int z) {
        int quad = findQuadrant(x, z);
        int split = (size + 1) >> 1;
        if (children != null) {
            for (int i = children.size(); --i >= 0;) {
                Spatial spat = children.get(i);
                int col = x;
                int row = z;
                boolean match = false;

                // get the childs quadrant
                int childQuadrant = 0;
                if (spat instanceof TerrainQuad) {
                    childQuadrant = ((TerrainQuad) spat).getQuadrant();
                } else if (spat instanceof TerrainPatch) {
                    childQuadrant = ((TerrainPatch) spat).getQuadrant();
                }

                if (childQuadrant == 1 && (quad & 1) != 0) {
                    match = true;
                } else if (childQuadrant == 2 && (quad & 2) != 0) {
                    row = z - split + 1;
                    match = true;
                } else if (childQuadrant == 3 && (quad & 4) != 0) {
                    col = x - split + 1;
                    match = true;
                } else if (childQuadrant == 4 && (quad & 8) != 0) {
                    col = x - split + 1;
                    row = z - split + 1;
                    match = true;
                }
                if (match)
                    return new QuadrantChild(col, row, spat);
            }
        }
        return null;
    }

    /**
     * Get the interpolated height of the terrain at the specified point.
     * @param xz the location to get the height for
     * @return Float.NAN if the value does not exist, or the coordinates are outside of the terrain
     */
    public float getHeight(Vector2f xz) {
        // offset
        float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)(totalSize-1) / 2f);
        float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)(totalSize-1) / 2f);
        if (!isInside((int)x, (int)z))
            return Float.NaN;
        float height = getHeight((int)x, (int)z, (x%1f), (z%1f));
        height *= getWorldScale().y;
        return height;
    }

    /*
     * gets an interpolated value at the specified point
     */
    protected float getHeight(int x, int z, float xm, float zm) {

        QuadrantChild match = findMatchingChild(x,z);
        if (match != null) {
            if (match.child instanceof TerrainQuad) {
                return ((TerrainQuad) match.child).getHeight(match.col, match.row, xm, zm);
            } else if (match.child instanceof TerrainPatch) {
                return ((TerrainPatch) match.child).getHeight(match.col, match.row, xm, zm);
            }
        }
        return Float.NaN;
    }

    public Vector3f getNormal(Vector2f xz) {
        // offset
        float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)(totalSize-1) / 2f);
        float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)(totalSize-1) / 2f);
        Vector3f normal = getNormal(x, z, xz);

        return normal;
    }

    protected Vector3f getNormal(float x, float z, Vector2f xz) {
        x-=0.5f;
        z-=0.5f;
        float col = FastMath.floor(x);
        float row = FastMath.floor(z);
        boolean onX = false;
        if(1 - (x - col)-(z - row) < 0) // what triangle to interpolate on
            onX = true;
        // v1--v2  ^
        // |  / |  |
        // | /  |  |
        // v3--v4  | Z
        //         |
        // <-------Y
        //     X
        Vector3f n1 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.ceil(z));
        Vector3f n2 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.ceil(z));
        Vector3f n3 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.floor(z));
        Vector3f n4 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.floor(z));

        return n1.add(n2).add(n3).add(n4).normalize();
    }

    public void setHeight(Vector2f xz, float height) {
        List<Vector2f> coord = new ArrayList<Vector2f>();
        coord.add(xz);
        List<Float> h = new ArrayList<Float>();
        h.add(height);

        setHeight(coord, h);
    }

    public void adjustHeight(Vector2f xz, float delta) {
        List<Vector2f> coord = new ArrayList<Vector2f>();
        coord.add(xz);
        List<Float> h = new ArrayList<Float>();
        h.add(delta);

        adjustHeight(coord, h);
    }

    public void setHeight(List<Vector2f> xz, List<Float> height) {
        setHeight(xz, height, true);
    }

    public void adjustHeight(List<Vector2f> xz, List<Float> height) {
        setHeight(xz, height, false);
    }

    protected void setHeight(List<Vector2f> xz, List<Float> height, boolean overrideHeight) {
        if (xz.size() != height.size())
            throw new IllegalArgumentException("Both lists must be the same length!");

        int halfSize = totalSize / 2;

        List<LocationHeight> locations = new ArrayList<LocationHeight>();

        // offset
        for (int i=0; i<xz.size(); i++) {
            int x = Math.round((xz.get(i).x / getWorldScale().x) + halfSize);
            int z = Math.round((xz.get(i).y / getWorldScale().z) + halfSize);
            if (!isInside(x, z))
                continue;
            locations.add(new LocationHeight(x,z,height.get(i)));
        }

        setHeight(locations, overrideHeight); // adjust height of the actual mesh

        // signal that the normals need updating
        for (int i=0; i<xz.size(); i++)
            setNormalRecalcNeeded(xz.get(i) );
    }

    protected class LocationHeight {
        int x;
        int z;
        float h;

        LocationHeight(){}

        LocationHeight(int x, int z, float h){
            this.x = x;
            this.z = z;
            this.h = h;
        }
    }

    protected void setHeight(List<LocationHeight> locations, boolean overrideHeight) {
        if (children == null)
            return;

        List<LocationHeight> quadLH1 = new ArrayList<LocationHeight>();
        List<LocationHeight> quadLH2 = new ArrayList<LocationHeight>();
        List<LocationHeight> quadLH3 = new ArrayList<LocationHeight>();
        List<LocationHeight> quadLH4 = new ArrayList<LocationHeight>();
        Spatial quad1 = null;
        Spatial quad2 = null;
        Spatial quad3 = null;
        Spatial quad4 = null;

        // get the child quadrants
        for (int i = children.size(); --i >= 0;) {
            Spatial spat = children.get(i);
            int childQuadrant = 0;
            if (spat instanceof TerrainQuad) {
                childQuadrant = ((TerrainQuad) spat).getQuadrant();
            } else if (spat instanceof TerrainPatch) {
                childQuadrant = ((TerrainPatch) spat).getQuadrant();
            }

            if (childQuadrant == 1)
                quad1 = spat;
            else if (childQuadrant == 2)
                quad2 = spat;
            else if (childQuadrant == 3)
                quad3 = spat;
            else if (childQuadrant == 4)
                quad4 = spat;
        }

        int split = (size + 1) >> 1;

        // distribute each locationHeight into the quadrant it intersects
        for (LocationHeight lh : locations) {
            int quad = findQuadrant(lh.x, lh.z);
            int col = lh.x;
            int row = lh.z;

            if ((quad & 1) != 0) {
                quadLH1.add(lh);
            }
            if ((quad & 2) != 0) {
                row = lh.z - split + 1;
                quadLH2.add(new LocationHeight(lh.x, row, lh.h));
            }
            if ((quad & 4) != 0) {
                col = lh.x - split + 1;
                quadLH3.add(new LocationHeight(col, lh.z, lh.h));
            }
            if ((quad & 8) != 0) {
                col = lh.x - split + 1;
                row = lh.z - split + 1;
                quadLH4.add(new LocationHeight(col, row, lh.h));
            }
        }

        // send the locations to the children
        if (!quadLH1.isEmpty()) {
            if (quad1 instanceof TerrainQuad)
                ((TerrainQuad)quad1).setHeight(quadLH1, overrideHeight);
            else if(quad1 instanceof TerrainPatch)
                ((TerrainPatch)quad1).setHeight(quadLH1, overrideHeight);
        }

        if (!quadLH2.isEmpty()) {
            if (quad2 instanceof TerrainQuad)
                ((TerrainQuad)quad2).setHeight(quadLH2, overrideHeight);
            else if(quad2 instanceof TerrainPatch)
                ((TerrainPatch)quad2).setHeight(quadLH2, overrideHeight);
        }

        if (!quadLH3.isEmpty()) {
            if (quad3 instanceof TerrainQuad)
                ((TerrainQuad)quad3).setHeight(quadLH3, overrideHeight);
            else if(quad3 instanceof TerrainPatch)
                ((TerrainPatch)quad3).setHeight(quadLH3, overrideHeight);
        }

        if (!quadLH4.isEmpty()) {
            if (quad4 instanceof TerrainQuad)
                ((TerrainQuad)quad4).setHeight(quadLH4, overrideHeight);
            else if(quad4 instanceof TerrainPatch)
                ((TerrainPatch)quad4).setHeight(quadLH4, overrideHeight);
        }
    }

    protected boolean isPointOnTerrain(int x, int z) {
        return (x >= 0 && x <= totalSize && z >= 0 && z <= totalSize);
    }


    public int getTerrainSize() {
        return totalSize;
    }


    // a position can be in multiple quadrants, so use a bit anded value.
    private int findQuadrant(int x, int y) {
        int split = (size + 1) >> 1;
        int quads = 0;
        if (x < split && y < split)
            quads |= 1;
        if (x < split && y >= split - 1)
            quads |= 2;
        if (x >= split - 1 && y < split)
            quads |= 4;
        if (x >= split - 1 && y >= split - 1)
            quads |= 8;
        return quads;
    }

    /**
     * lock or unlock the meshes of this terrain.
     * Locked meshes are uneditable but have better performance.
     * @param locked or unlocked
     */
    public void setLocked(boolean locked) {
        for (int i = 0; i < this.getQuantity(); i++) {
            if (this.getChild(i) instanceof TerrainQuad) {
                ((TerrainQuad) getChild(i)).setLocked(locked);
            } else if (this.getChild(i) instanceof TerrainPatch) {
                if (locked)
                    ((TerrainPatch) getChild(i)).lockMesh();
                else
                    ((TerrainPatch) getChild(i)).unlockMesh();
            }
        }
    }


    public int getQuadrant() {
        return quadrant;
    }

    public void setQuadrant(short quadrant) {
        this.quadrant = quadrant;
    }


    protected TerrainPatch getPatch(int quad) {
        if (children != null)
            for (int x = children.size(); --x >= 0;) {
                Spatial child = children.get(x);
                if (child instanceof TerrainPatch) {
                    TerrainPatch tb = (TerrainPatch) child;
                    if (tb.getQuadrant() == quad)
                        return tb;
                }
            }
        return null;
    }

    protected TerrainQuad getQuad(int quad) {
        if (quad == 0)
            return this;
        if (children != null)
            for (int x = children.size(); --x >= 0;) {
                Spatial child = children.get(x);
                if (child instanceof TerrainQuad) {
                    TerrainQuad tq = (TerrainQuad) child;
                    if (tq.getQuadrant() == quad)
                        return tq;
                }
            }
        return null;
    }

    protected TerrainPatch findRightPatch(TerrainPatch tp) {
        if (tp.getQuadrant() == 1)
            return getPatch(3);
        else if (tp.getQuadrant() == 2)
            return getPatch(4);
        else if (tp.getQuadrant() == 3) {
            // find the patch to the right and ask it for child 1.
            TerrainQuad quad = findRightQuad();
            if (quad != null)
                return quad.getPatch(1);
        } else if (tp.getQuadrant() == 4) {
            // find the patch to the right and ask it for child 2.
            TerrainQuad quad = findRightQuad();
            if (quad != null)
                return quad.getPatch(2);
        }

        return null;
    }

    protected TerrainPatch findDownPatch(TerrainPatch tp) {
        if (tp.getQuadrant() == 1)
            return getPatch(2);
        else if (tp.getQuadrant() == 3)
            return getPatch(4);
        else if (tp.getQuadrant() == 2) {
            // find the patch below and ask it for child 1.
            TerrainQuad quad = findDownQuad();
            if (quad != null)
                return quad.getPatch(1);
        } else if (tp.getQuadrant() == 4) {
            TerrainQuad quad = findDownQuad();
            if (quad != null)
                return quad.getPatch(3);
        }

        return null;
    }


    protected TerrainPatch findTopPatch(TerrainPatch tp) {
        if (tp.getQuadrant() == 2)
            return getPatch(1);
        else if (tp.getQuadrant() == 4)
            return getPatch(3);
        else if (tp.getQuadrant() == 1) {
            // find the patch above and ask it for child 2.
            TerrainQuad quad = findTopQuad();
            if (quad != null)
                return quad.getPatch(2);
        } else if (tp.getQuadrant() == 3) {
            TerrainQuad quad = findTopQuad();
            if (quad != null)
                return quad.getPatch(4);
        }

        return null;
    }

    protected TerrainPatch findLeftPatch(TerrainPatch tp) {
        if (tp.getQuadrant() == 3)
            return getPatch(1);
        else if (tp.getQuadrant() == 4)
            return getPatch(2);
        else if (tp.getQuadrant() == 1) {
            // find the patch above and ask it for child 3.
            TerrainQuad quad = findLeftQuad();
            if (quad != null)
                return quad.getPatch(3);
        } else if (tp.getQuadrant() == 2) {
            TerrainQuad quad = findLeftQuad();
            if (quad != null)
                return quad.getPatch(4);
        }

        return null;
    }

    protected TerrainQuad findRightQuad() {
        boolean useFinder = false;
        if (getParent() == null || !(getParent() instanceof TerrainQuad)) {
            if (neighbourFinder == null)
                return null;
            else
                useFinder = true;
        }

        TerrainQuad pQuad = null;
        if (!useFinder)
            pQuad = (TerrainQuad) getParent();

        if (quadrant == 1)
            return pQuad.getQuad(3);
        else if (quadrant == 2)
            return pQuad.getQuad(4);
        else if (quadrant == 3) {
            TerrainQuad quad = pQuad.findRightQuad();
            if (quad != null)
                return quad.getQuad(1);
        } else if (quadrant == 4) {
            TerrainQuad quad = pQuad.findRightQuad();
            if (quad != null)
                return quad.getQuad(2);
        } else if (quadrant == 0) {
            // at the top quad
            if (useFinder) {
                TerrainQuad quad = neighbourFinder.getRightQuad(this);
                return quad;
            }
        }

        return null;
    }

    protected TerrainQuad findDownQuad() {
        boolean useFinder = false;
        if (getParent() == null || !(getParent() instanceof TerrainQuad)) {
            if (neighbourFinder == null)
                return null;
            else
                useFinder = true;
        }

        TerrainQuad pQuad = null;
        if (!useFinder)
            pQuad = (TerrainQuad) getParent();

        if (quadrant == 1)
            return pQuad.getQuad(2);
        else if (quadrant == 3)
            return pQuad.getQuad(4);
        else if (quadrant == 2) {
            TerrainQuad quad = pQuad.findDownQuad();
            if (quad != null)
                return quad.getQuad(1);
        } else if (quadrant == 4) {
            TerrainQuad quad = pQuad.findDownQuad();
            if (quad != null)
                return quad.getQuad(3);
        } else if (quadrant == 0) {
            // at the top quad
            if (useFinder) {
                TerrainQuad quad = neighbourFinder.getDownQuad(this);
                return quad;
            }
        }

        return null;
    }

    protected TerrainQuad findTopQuad() {
        boolean useFinder = false;
        if (getParent() == null || !(getParent() instanceof TerrainQuad)) {
            if (neighbourFinder == null)
                return null;
            else
                useFinder = true;
        }

        TerrainQuad pQuad = null;
        if (!useFinder)
            pQuad = (TerrainQuad) getParent();

        if (quadrant == 2)
            return pQuad.getQuad(1);
        else if (quadrant == 4)
            return pQuad.getQuad(3);
        else if (quadrant == 1) {
            TerrainQuad quad = pQuad.findTopQuad();
            if (quad != null)
                return quad.getQuad(2);
        } else if (quadrant == 3) {
            TerrainQuad quad = pQuad.findTopQuad();
            if (quad != null)
                return quad.getQuad(4);
        } else if (quadrant == 0) {
            // at the top quad
            if (useFinder) {
                TerrainQuad quad = neighbourFinder.getTopQuad(this);
                return quad;
            }
        }

        return null;
    }

    protected TerrainQuad findLeftQuad() {
        boolean useFinder = false;
        if (getParent() == null || !(getParent() instanceof TerrainQuad)) {
            if (neighbourFinder == null)
                return null;
            else
                useFinder = true;
        }

        TerrainQuad pQuad = null;
        if (!useFinder)
            pQuad = (TerrainQuad) getParent();

        if (quadrant == 3)
            return pQuad.getQuad(1);
        else if (quadrant == 4)
            return pQuad.getQuad(2);
        else if (quadrant == 1) {
            TerrainQuad quad = pQuad.findLeftQuad();
            if (quad != null)
                return quad.getQuad(3);
        } else if (quadrant == 2) {
            TerrainQuad quad = pQuad.findLeftQuad();
            if (quad != null)
                return quad.getQuad(4);
        } else if (quadrant == 0) {
            // at the top quad
            if (useFinder) {
                TerrainQuad quad = neighbourFinder.getLeftQuad(this);
                return quad;
            }
        }

        return null;
    }

    /**
     * Find what terrain patches need normal recalculations and update
     * their normals;
     */
    protected void fixNormals(BoundingBox affectedArea) {
        if (children == null)
            return;

        // go through the children and see if they collide with the affectedAreaBBox
        // if they do, then update their normals
        for (int x = children.size(); --x >= 0;) {
            Spatial child = children.get(x);
            if (child instanceof TerrainQuad) {
                if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound()) )
                    ((TerrainQuad) child).fixNormals(affectedArea);
            } else if (child instanceof TerrainPatch) {
                if (affectedArea != null && affectedArea.intersects(((TerrainPatch) child).getWorldBound()) )
                    ((TerrainPatch) child).updateNormals(); // recalculate the patch's normals
            }
        }
    }

    /**
     * fix the normals on the edge of the terrain patches.
     */
    protected void fixNormalEdges(BoundingBox affectedArea) {
        if (children == null)
            return;

        for (int x = children.size(); --x >= 0;) {
            Spatial child = children.get(x);
            if (child instanceof TerrainQuad) {
                if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound()) )
                    ((TerrainQuad) child).fixNormalEdges(affectedArea);
            } else if (child instanceof TerrainPatch) {
                if (affectedArea != null && !affectedArea.intersects(((TerrainPatch) child).getWorldBound()) ) // if doesn't intersect, continue
                    continue;

                TerrainPatch tp = (TerrainPatch) child;
                TerrainPatch right = findRightPatch(tp);
                TerrainPatch bottom = findDownPatch(tp);
                TerrainPatch top = findTopPatch(tp);
                TerrainPatch left = findLeftPatch(tp);
                TerrainPatch topLeft = null;
                if (top != null)
                    topLeft = findLeftPatch(top);
                TerrainPatch bottomRight = null;
                if (right != null)
                    bottomRight = findDownPatch(right);
                TerrainPatch topRight = null;
                if (top != null)
                    topRight = findRightPatch(top);
                TerrainPatch bottomLeft = null;
                if (left != null)
                    bottomLeft = findDownPatch(left);

                tp.fixNormalEdges(right, bottom, top, left, bottomRight, bottomLeft, topRight, topLeft);

            }
        } // for each child

    }



    @Override
    public int collideWith(Collidable other, CollisionResults results){
        int total = 0;

        if (other instanceof Ray)
            return collideWithRay((Ray)other, results);

        // if it didn't collide with this bbox, return
        if (other instanceof BoundingVolume)
            if (!this.getWorldBound().intersects((BoundingVolume)other))
                return total;

        for (Spatial child : children){
            total += child.collideWith(other, results);
        }
        return total;
    }

    /**
     * Gather the terrain patches that intersect the given ray (toTest).
     * This only tests the bounding boxes
     * @param toTest
     * @param results
     */
    public void findPick(Ray toTest, List<TerrainPickData> results) {

        if (getWorldBound() != null) {
            if (getWorldBound().intersects(toTest)) {
                // further checking needed.
                for (int i = 0; i < getQuantity(); i++) {
                    if (children.get(i) instanceof TerrainPatch) {
                        TerrainPatch tp = (TerrainPatch) children.get(i);
                        tp.ensurePositiveVolumeBBox();
                        if (tp.getWorldBound().intersects(toTest)) {
                            CollisionResults cr = new CollisionResults();
                            toTest.collideWith(tp.getWorldBound(), cr);
                            if (cr != null && cr.getClosestCollision() != null) {
                                cr.getClosestCollision().getDistance();
                                results.add(new TerrainPickData(tp, cr.getClosestCollision()));
                            }
                        }
                    }
                    else if (children.get(i) instanceof TerrainQuad) {
                        ((TerrainQuad) children.get(i)).findPick(toTest, results);
                    }
                }
            }
        }
    }


    /**
     * Retrieve all Terrain Patches from all children and store them
     * in the 'holder' list
     * @param holder must not be null, will be populated when returns
     */
    public void getAllTerrainPatches(List<TerrainPatch> holder) {
        if (children != null) {
            for (int i = children.size(); --i >= 0;) {
                Spatial child = children.get(i);
                if (child instanceof TerrainQuad) {
                    ((TerrainQuad) child).getAllTerrainPatches(holder);
                } else if (child instanceof TerrainPatch) {
                    holder.add((TerrainPatch)child);
                }
            }
        }
    }

    public void getAllTerrainPatchesWithTranslation(Map<TerrainPatch,Vector3f> holder, Vector3f translation) {
        if (children != null) {
            for (int i = children.size(); --i >= 0;) {
                Spatial child = children.get(i);
                if (child instanceof TerrainQuad) {
                    ((TerrainQuad) child).getAllTerrainPatchesWithTranslation(holder, translation.clone().add(child.getLocalTranslation()));
                } else if (child instanceof TerrainPatch) {
                    //if (holder.size() < 4)
                    holder.put((TerrainPatch)child, translation.clone().add(child.getLocalTranslation()));
                }
            }
        }
    }

    @Override
    public void read(JmeImporter e) throws IOException {
        super.read(e);
        InputCapsule c = e.getCapsule(this);
        size = c.readInt("size", 0);
        stepScale = (Vector3f) c.readSavable("stepScale", null);
        offset = (Vector2f) c.readSavable("offset", new Vector2f(0,0));
        offsetAmount = c.readFloat("offsetAmount", 0);
        quadrant = c.readInt("quadrant", 0);
        totalSize = c.readInt("totalSize", 0);
        //lodCalculator = (LodCalculator) c.readSavable("lodCalculator", createDefaultLodCalculator());
        //lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null);

        if ( !(getParent() instanceof TerrainQuad) ) {
            BoundingBox all = new BoundingBox(getWorldTranslation(), totalSize, totalSize, totalSize);
            affectedAreaBBox = all;
            updateNormals();
        }
    }

    @Override
    public void write(JmeExporter e) throws IOException {
        super.write(e);
        OutputCapsule c = e.getCapsule(this);
        c.write(size, "size", 0);
        c.write(totalSize, "totalSize", 0);
        c.write(stepScale, "stepScale", null);
        c.write(offset, "offset", new Vector2f(0,0));
        c.write(offsetAmount, "offsetAmount", 0);
        c.write(quadrant, "quadrant", 0);
        //c.write(lodCalculatorFactory, "lodCalculatorFactory", null);
        //c.write(lodCalculator, "lodCalculator", null);
    }

    @Override
    public TerrainQuad clone() {
        return this.clone(true);
    }

	@Override
    public TerrainQuad clone(boolean cloneMaterials) {
        TerrainQuad quadClone = (TerrainQuad) super.clone(cloneMaterials);
        quadClone.name = name.toString();
        quadClone.size = size;
        quadClone.totalSize = totalSize;
        if (stepScale != null) {
            quadClone.stepScale = stepScale.clone();
        }
        if (offset != null) {
            quadClone.offset = offset.clone();
        }
        quadClone.offsetAmount = offsetAmount;
        quadClone.quadrant = quadrant;
        //quadClone.lodCalculatorFactory = lodCalculatorFactory.clone();
        //quadClone.lodCalculator = lodCalculator.clone();

        TerrainLodControl lodControlCloned = this.getControl(TerrainLodControl.class);
        TerrainLodControl lodControl = quadClone.getControl(TerrainLodControl.class);

        if (lodControlCloned != null && !(getParent() instanceof TerrainQuad)) {
            //lodControlCloned.setLodCalculator(lodControl.getLodCalculator().clone());
        }
        NormalRecalcControl normalControl = getControl(NormalRecalcControl.class);
        if (normalControl != null)
            normalControl.setTerrain(this);

        return quadClone;
    }

    /**
     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
     */
    @Override
    public void cloneFields( Cloner cloner, Object original ) {
        super.cloneFields(cloner, original);

        this.stepScale = cloner.clone(stepScale);
        this.offset = cloner.clone(offset);

        // This was not cloned before... I think that's a mistake.
        this.affectedAreaBBox = cloner.clone(affectedAreaBBox);

        // picker is not cloneable and not cloned.  This also seems like
        // a mistake if you ever load the same terrain twice.
        // this.picker = cloner.clone(picker);

        // neighbourFinder is also not cloned.  Maybe that's ok.
    }

    @Override
    protected void setParent(Node parent) {
        super.setParent(parent);
        if (parent == null) {
            // if the terrain is being detached
            clearCaches();
        }
    }

    /**
     * Removes any cached references this terrain is holding, in particular
     * the TerrainPatch's neighbour references.
     * This is called automatically when the root terrainQuad is detached from
     * its parent or if setParent(null) is called.
     */
    public void clearCaches() {
        if (children != null) {
            for (int i = children.size(); --i >= 0;) {
                Spatial child = children.get(i);
                if (child instanceof TerrainQuad) {
                    ((TerrainQuad) child).clearCaches();
                } else if (child instanceof TerrainPatch) {
                    ((TerrainPatch) child).clearCaches();
                }
            }
        }
    }

    public int getMaxLod() {
        if (maxLod < 0)
            maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide

        return maxLod;
    }

    public int getPatchSize() {
        return patchSize;
    }

    public int getTotalSize() {
        return totalSize;
    }

    public float[] getHeightMap() {

        float[] hm = null;
        int length = ((size-1)/2)+1;
        int area = size*size;
        hm = new float[area];

        if (getChildren() != null && !getChildren().isEmpty()) {
            float[] ul=null, ur=null, bl=null, br=null;
            // get the child heightmaps
            if (getChild(0) instanceof TerrainPatch) {
                for (Spatial s : getChildren()) {
                    if ( ((TerrainPatch)s).getQuadrant() == 1)
                        ul = ((TerrainPatch)s).getHeightMap();
                    else if(((TerrainPatch) s).getQuadrant() == 2)
                        bl = ((TerrainPatch)s).getHeightMap();
                    else if(((TerrainPatch) s).getQuadrant() == 3)
                        ur = ((TerrainPatch)s).getHeightMap();
                    else if(((TerrainPatch) s).getQuadrant() == 4)
                        br = ((TerrainPatch)s).getHeightMap();
                }
            }
            else {
                ul = getQuad(1).getHeightMap();
                bl = getQuad(2).getHeightMap();
                ur = getQuad(3).getHeightMap();
                br = getQuad(4).getHeightMap();
            }

            // combine them into a single heightmap


            // first upper blocks
            for (int y=0; y<length; y++) { // rows
                for (int x1=0; x1<length; x1++) {
                    int row = y*size;
                    hm[row+x1] = ul[y*length+x1];
                }
                for (int x2=1; x2<length; x2++) {
                    int row = y*size + length;
                    hm[row+x2-1] = ur[y*length + x2];
                }
            }
            // second lower blocks
            int rowOffset = size*length;
            for (int y=1; y<length; y++) { // rows
                for (int x1=0; x1<length; x1++) {
                    int row = (y-1)*size;
                    hm[rowOffset+row+x1] = bl[y*length+x1];
                }
                for (int x2=1; x2<length; x2++) {
                    int row = (y-1)*size + length;
                    hm[rowOffset+row+x2-1] = br[y*length + x2];
                }
            }
        }

        return hm;
    }
}

