Home | History | Annotate | Download | only in geomipmap
      1 /*
      2  * Copyright (c) 2009-2012 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.jme3.terrain.geomipmap;
     34 
     35 import com.jme3.bounding.BoundingBox;
     36 import com.jme3.bounding.BoundingSphere;
     37 import com.jme3.bounding.BoundingVolume;
     38 import com.jme3.collision.Collidable;
     39 import com.jme3.collision.CollisionResults;
     40 import com.jme3.collision.UnsupportedCollisionException;
     41 import com.jme3.export.InputCapsule;
     42 import com.jme3.export.JmeExporter;
     43 import com.jme3.export.JmeImporter;
     44 import com.jme3.export.OutputCapsule;
     45 import com.jme3.math.*;
     46 import com.jme3.scene.Geometry;
     47 import com.jme3.scene.Mesh;
     48 import com.jme3.scene.VertexBuffer;
     49 import com.jme3.scene.VertexBuffer.Type;
     50 import com.jme3.terrain.geomipmap.TerrainQuad.LocationHeight;
     51 import com.jme3.terrain.geomipmap.lodcalc.util.EntropyComputeUtil;
     52 import com.jme3.util.BufferUtils;
     53 import java.io.IOException;
     54 import java.nio.FloatBuffer;
     55 import java.nio.IntBuffer;
     56 import java.util.HashMap;
     57 import java.util.List;
     58 
     59 
     60 /**
     61  * A terrain patch is a leaf in the terrain quad tree. It has a mesh that can change levels of detail (LOD)
     62  * whenever the view point, or camera, changes. The actual terrain mesh is created by the LODGeomap class.
     63  * That uses a geo-mipmapping algorithm to change the index buffer of the mesh.
     64  * The mesh is a triangle strip. In wireframe mode you might notice some strange lines, these are degenerate
     65  * triangles generated by the geoMipMap algorithm and can be ignored. The video card removes them at almost no cost.
     66  *
     67  * Each patch needs to know its neighbour's LOD so it can seam its edges with them, in case the neighbour has a different
     68  * LOD. If this doesn't happen, you will see gaps.
     69  *
     70  * The LOD value is most detailed at zero. It gets less detailed the higher the LOD value until you reach maxLod, which
     71  * is a mathematical limit on the number of times the 'size' of the patch can be divided by two. However there is a -1 to that
     72  * for now until I add in a custom index buffer calculation for that max level, the current algorithm does not go that far.
     73  *
     74  * You can supply a LodThresholdCalculator for use in determining when the LOD should change. It's API will no doubt change
     75  * in the near future. Right now it defaults to just changing LOD every two patch sizes. So if a patch has a size of 65,
     76  * then the LOD changes every 130 units away.
     77  *
     78  * @author Brent Owens
     79  */
     80 public class TerrainPatch extends Geometry {
     81 
     82     protected LODGeomap geomap;
     83     protected int lod = -1; // this terrain patch's LOD
     84     private int maxLod = -1;
     85     protected int previousLod = -1;
     86     protected int lodLeft, lodTop, lodRight, lodBottom; // it's neighbour's LODs
     87 
     88     protected int size;
     89 
     90     protected int totalSize;
     91 
     92     protected short quadrant = 1;
     93 
     94     // x/z step
     95     protected Vector3f stepScale;
     96 
     97     // center of the patch in relation to (0,0,0)
     98     protected Vector2f offset;
     99 
    100     // amount the patch has been shifted.
    101     protected float offsetAmount;
    102 
    103     //protected LodCalculator lodCalculator;
    104     //protected LodCalculatorFactory lodCalculatorFactory;
    105 
    106     protected TerrainPatch leftNeighbour, topNeighbour, rightNeighbour, bottomNeighbour;
    107     protected boolean searchedForNeighboursAlready = false;
    108 
    109 
    110     protected float[] lodEntropy;
    111 
    112     public TerrainPatch() {
    113         super("TerrainPatch");
    114     }
    115 
    116     public TerrainPatch(String name) {
    117         super(name);
    118     }
    119 
    120     public TerrainPatch(String name, int size) {
    121         this(name, size, new Vector3f(1,1,1), null, new Vector3f(0,0,0));
    122     }
    123 
    124     /**
    125      * Constructor instantiates a new <code>TerrainPatch</code> object. The
    126      * parameters and heightmap data are then processed to generate a
    127      * <code>TriMesh</code> object for rendering.
    128      *
    129      * @param name
    130      *			the name of the terrain patch.
    131      * @param size
    132      *			the size of the heightmap.
    133      * @param stepScale
    134      *			the scale for the axes.
    135      * @param heightMap
    136      *			the height data.
    137      * @param origin
    138      *			the origin offset of the patch.
    139      */
    140     public TerrainPatch(String name, int size, Vector3f stepScale,
    141                     float[] heightMap, Vector3f origin) {
    142         this(name, size, stepScale, heightMap, origin, size, new Vector2f(), 0);
    143     }
    144 
    145     /**
    146      * Constructor instantiates a new <code>TerrainPatch</code> object. The
    147      * parameters and heightmap data are then processed to generate a
    148      * <code>TriMesh</code> object for renderering.
    149      *
    150      * @param name
    151      *			the name of the terrain patch.
    152      * @param size
    153      *			the size of the patch.
    154      * @param stepScale
    155      *			the scale for the axes.
    156      * @param heightMap
    157      *			the height data.
    158      * @param origin
    159      *			the origin offset of the patch.
    160      * @param totalSize
    161      *			the total size of the terrain. (Higher if the patch is part of
    162      *			a <code>TerrainQuad</code> tree.
    163      * @param offset
    164      *			the offset for texture coordinates.
    165      * @param offsetAmount
    166      *			the total offset amount. Used for texture coordinates.
    167      */
    168     public TerrainPatch(String name, int size, Vector3f stepScale,
    169                     float[] heightMap, Vector3f origin, int totalSize,
    170                     Vector2f offset, float offsetAmount) {
    171         super(name);
    172         this.size = size;
    173         this.stepScale = stepScale;
    174         this.totalSize = totalSize;
    175         this.offsetAmount = offsetAmount;
    176         this.offset = offset;
    177 
    178         setLocalTranslation(origin);
    179 
    180         geomap = new LODGeomap(size, heightMap);
    181         Mesh m = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false);
    182         setMesh(m);
    183 
    184     }
    185 
    186     /**
    187      * This calculation is slow, so don't use it often.
    188      */
    189     public void generateLodEntropies() {
    190         float[] entropies = new float[getMaxLod()+1];
    191         for (int i = 0; i <= getMaxLod(); i++){
    192             int curLod = (int) Math.pow(2, i);
    193             IntBuffer buf = geomap.writeIndexArrayLodDiff(null, curLod, false, false, false, false);
    194             entropies[i] = EntropyComputeUtil.computeLodEntropy(mesh, buf);
    195         }
    196 
    197         lodEntropy = entropies;
    198     }
    199 
    200     public float[] getLodEntropies(){
    201         if (lodEntropy == null){
    202             generateLodEntropies();
    203         }
    204         return lodEntropy;
    205     }
    206 
    207     @Deprecated
    208     public FloatBuffer getHeightmap() {
    209         return BufferUtils.createFloatBuffer(geomap.getHeightArray());
    210     }
    211 
    212     public float[] getHeightMap() {
    213         return geomap.getHeightArray();
    214     }
    215 
    216     /**
    217      * The maximum lod supported by this terrain patch.
    218      * If the patch size is 32 then the returned value would be log2(32)-2 = 3
    219      * You can then use that value, 3, to see how many times you can divide 32 by 2
    220      * before the terrain gets too un-detailed (can't stitch it any further).
    221      * @return
    222      */
    223     public int getMaxLod() {
    224         if (maxLod < 0)
    225             maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide
    226 
    227         return maxLod;
    228     }
    229 
    230     protected void reIndexGeometry(HashMap<String,UpdatedTerrainPatch> updated, boolean useVariableLod) {
    231 
    232         UpdatedTerrainPatch utp = updated.get(getName());
    233 
    234         if (utp != null && (utp.isReIndexNeeded() || utp.isFixEdges()) ) {
    235             int pow = (int) Math.pow(2, utp.getNewLod());
    236             boolean left = utp.getLeftLod() > utp.getNewLod();
    237             boolean top = utp.getTopLod() > utp.getNewLod();
    238             boolean right = utp.getRightLod() > utp.getNewLod();
    239             boolean bottom = utp.getBottomLod() > utp.getNewLod();
    240 
    241             IntBuffer ib = null;
    242             if (useVariableLod)
    243                 ib = geomap.writeIndexArrayLodVariable(null, pow, (int) Math.pow(2, utp.getRightLod()), (int) Math.pow(2, utp.getTopLod()), (int) Math.pow(2, utp.getLeftLod()), (int) Math.pow(2, utp.getBottomLod()));
    244             else
    245                 ib = geomap.writeIndexArrayLodDiff(null, pow, right, top, left, bottom);
    246             utp.setNewIndexBuffer(ib);
    247         }
    248 
    249     }
    250 
    251 
    252     public Vector2f getTex(float x, float z, Vector2f store) {
    253         if (x < 0 || z < 0 || x >= size || z >= size) {
    254             store.set(Vector2f.ZERO);
    255             return store;
    256         }
    257         int idx = (int) (z * size + x);
    258         return store.set(getMesh().getFloatBuffer(Type.TexCoord).get(idx*2),
    259                          getMesh().getFloatBuffer(Type.TexCoord).get(idx*2+1) );
    260     }
    261 
    262     public float getHeightmapHeight(float x, float z) {
    263         if (x < 0 || z < 0 || x >= size || z >= size)
    264             return 0;
    265         int idx = (int) (z * size + x);
    266         return getMesh().getFloatBuffer(Type.Position).get(idx*3+1); // 3 floats per entry (x,y,z), the +1 is to get the Y
    267     }
    268 
    269     /**
    270      * Get the triangle of this geometry at the specified local coordinate.
    271      * @param x local to the terrain patch
    272      * @param z local to the terrain patch
    273      * @return the triangle in world coordinates, or null if the point does intersect this patch on the XZ axis
    274      */
    275     public Triangle getTriangle(float x, float z) {
    276         return geomap.getTriangleAtPoint(x, z, getWorldScale() , getWorldTranslation());
    277     }
    278 
    279     /**
    280      * Get the triangles at the specified grid point. Probably only 2 triangles
    281      * @param x local to the terrain patch
    282      * @param z local to the terrain patch
    283      * @return the triangles in world coordinates, or null if the point does intersect this patch on the XZ axis
    284      */
    285     public Triangle[] getGridTriangles(float x, float z) {
    286         return geomap.getGridTrianglesAtPoint(x, z, getWorldScale() , getWorldTranslation());
    287     }
    288 
    289     protected void setHeight(List<LocationHeight> locationHeights, boolean overrideHeight) {
    290 
    291         for (LocationHeight lh : locationHeights) {
    292             if (lh.x < 0 || lh.z < 0 || lh.x >= size || lh.z >= size)
    293                 continue;
    294             int idx = lh.z * size + lh.x;
    295             if (overrideHeight) {
    296                 geomap.getHeightArray()[idx] = lh.h;
    297             } else {
    298                 float h = getMesh().getFloatBuffer(Type.Position).get(idx*3+1);
    299                 geomap.getHeightArray()[idx] = h+lh.h;
    300             }
    301 
    302         }
    303 
    304         FloatBuffer newVertexBuffer = geomap.writeVertexArray(null, stepScale, false);
    305         getMesh().clearBuffer(Type.Position);
    306         getMesh().setBuffer(Type.Position, 3, newVertexBuffer);
    307     }
    308 
    309     /**
    310      * recalculate all of the normal vectors in this terrain patch
    311      */
    312     protected void updateNormals() {
    313         FloatBuffer newNormalBuffer = geomap.writeNormalArray(null, getWorldScale());
    314         getMesh().getBuffer(Type.Normal).updateData(newNormalBuffer);
    315         FloatBuffer newTangentBuffer = null;
    316         FloatBuffer newBinormalBuffer = null;
    317         FloatBuffer[] tb = geomap.writeTangentArray(newNormalBuffer, newTangentBuffer, newBinormalBuffer, (FloatBuffer)getMesh().getBuffer(Type.TexCoord).getData(), getWorldScale());
    318         newTangentBuffer = tb[0];
    319         newBinormalBuffer = tb[1];
    320         getMesh().getBuffer(Type.Tangent).updateData(newTangentBuffer);
    321         getMesh().getBuffer(Type.Binormal).updateData(newBinormalBuffer);
    322     }
    323 
    324     private void setInBuffer(Mesh mesh, int index, Vector3f normal, Vector3f tangent, Vector3f binormal) {
    325         VertexBuffer NB = mesh.getBuffer(Type.Normal);
    326         VertexBuffer TB = mesh.getBuffer(Type.Tangent);
    327         VertexBuffer BB = mesh.getBuffer(Type.Binormal);
    328         BufferUtils.setInBuffer(normal, (FloatBuffer)NB.getData(), index);
    329         BufferUtils.setInBuffer(tangent, (FloatBuffer)TB.getData(), index);
    330         BufferUtils.setInBuffer(binormal, (FloatBuffer)BB.getData(), index);
    331         NB.setUpdateNeeded();
    332         TB.setUpdateNeeded();
    333         BB.setUpdateNeeded();
    334     }
    335 
    336     /**
    337      * Matches the normals along the edge of the patch with the neighbours.
    338      * Computes the normals for the right, bottom, left, and top edges of the
    339      * patch, and saves those normals in the neighbour's edges too.
    340      *
    341      * Takes 4 points (if has neighbour on that side) for each
    342      * point on the edge of the patch:
    343      *              *
    344      *              |
    345      *          *---x---*
    346      *              |
    347      *              *
    348      * It works across the right side of the patch, from the top down to
    349      * the bottom. Then it works on the bottom side of the patch, from the
    350      * left to the right.
    351      */
    352     protected void fixNormalEdges(TerrainPatch right,
    353                                 TerrainPatch bottom,
    354                                 TerrainPatch top,
    355                                 TerrainPatch left,
    356                                 TerrainPatch bottomRight,
    357                                 TerrainPatch bottomLeft,
    358                                 TerrainPatch topRight,
    359                                 TerrainPatch topLeft)
    360     {
    361         Vector3f rootPoint = new Vector3f();
    362         Vector3f rightPoint = new Vector3f();
    363         Vector3f leftPoint = new Vector3f();
    364         Vector3f topPoint = new Vector3f();
    365 
    366         Vector3f bottomPoint = new Vector3f();
    367 
    368         Vector3f tangent = new Vector3f();
    369         Vector3f binormal = new Vector3f();
    370         Vector3f normal = new Vector3f();
    371 
    372 
    373         int s = this.getSize()-1;
    374 
    375         if (right != null) { // right side,    works its way down
    376             for (int i=0; i<s+1; i++) {
    377                 rootPoint.set(0, this.getHeightmapHeight(s,i), 0);
    378                 leftPoint.set(-1, this.getHeightmapHeight(s-1,i), 0);
    379                 rightPoint.set(1, right.getHeightmapHeight(1,i), 0);
    380 
    381                 if (i == 0) { // top point
    382                     bottomPoint.set(0, this.getHeightmapHeight(s,i+1), 1);
    383 
    384                     if (top == null) {
    385                         averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint,  normal, tangent, binormal);
    386                         setInBuffer(this.getMesh(), s, normal, tangent, binormal);
    387                         setInBuffer(right.getMesh(), 0, normal, tangent, binormal);
    388                     } else {
    389                         topPoint.set(0, top.getHeightmapHeight(s,s-1), -1);
    390 
    391                         averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint,normal, tangent, binormal);
    392                         setInBuffer(this.getMesh(), s, normal, tangent, binormal);
    393                         setInBuffer(right.getMesh(), 0, normal, tangent, binormal);
    394                         setInBuffer(top.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal);
    395 
    396                         if (topRight != null) {
    397                     //        setInBuffer(topRight.getMesh(), (s+1)*s, normal, tangent, binormal);
    398                         }
    399                     }
    400                 } else if (i == s) { // bottom point
    401                     topPoint.set(0, this.getHeightmapHeight(s,s-1), -1);
    402 
    403                     if (bottom == null) {
    404                         averageNormalsTangents(topPoint, rootPoint, leftPoint, null, rightPoint, normal, tangent, binormal);
    405                         setInBuffer(this.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal);
    406                         setInBuffer(right.getMesh(), (s+1)*(s), normal, tangent, binormal);
    407                     } else {
    408                         bottomPoint.set(0, bottom.getHeightmapHeight(s,1), 1);
    409                         averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);
    410                         setInBuffer(this.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal);
    411                         setInBuffer(right.getMesh(), (s+1)*s, normal, tangent, binormal);
    412                         setInBuffer(bottom.getMesh(), s, normal, tangent, binormal);
    413 
    414                         if (bottomRight != null) {
    415                    //         setInBuffer(bottomRight.getMesh(), 0, normal, tangent, binormal);
    416                         }
    417                     }
    418                 } else { // all in the middle
    419                     topPoint.set(0, this.getHeightmapHeight(s,i-1), -1);
    420                     bottomPoint.set(0, this.getHeightmapHeight(s,i+1), 1);
    421                     averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);
    422                     setInBuffer(this.getMesh(), (s+1)*(i+1)-1, normal, tangent, binormal);
    423                     setInBuffer(right.getMesh(), (s+1)*(i), normal, tangent, binormal);
    424                 }
    425             }
    426         }
    427 
    428         if (left != null) { // left side,    works its way down
    429             for (int i=0; i<s+1; i++) {
    430                 rootPoint.set(0, this.getHeightmapHeight(0,i), 0);
    431                 leftPoint.set(-1, left.getHeightmapHeight(s-1,i), 0);
    432                 rightPoint.set(1, this.getHeightmapHeight(1,i), 0);
    433 
    434                 if (i == 0) { // top point
    435                     bottomPoint.set(0, this.getHeightmapHeight(0,i+1), 1);
    436 
    437                     if (top == null) {
    438                         averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);
    439                         setInBuffer(this.getMesh(), 0, normal, tangent, binormal);
    440                         setInBuffer(left.getMesh(), s, normal, tangent, binormal);
    441                     } else {
    442                         topPoint.set(0, top.getHeightmapHeight(0,s-1), -1);
    443 
    444                         averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);
    445                         setInBuffer(this.getMesh(), 0, normal, tangent, binormal);
    446                         setInBuffer(left.getMesh(), s, normal, tangent, binormal);
    447                         setInBuffer(top.getMesh(), (s+1)*s, normal, tangent, binormal);
    448 
    449                         if (topLeft != null) {
    450                      //       setInBuffer(topLeft.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal);
    451                         }
    452                     }
    453                 } else if (i == s) { // bottom point
    454                     topPoint.set(0, this.getHeightmapHeight(0,i-1), -1);
    455 
    456                     if (bottom == null) {
    457                         averageNormalsTangents(topPoint, rootPoint, leftPoint, null, rightPoint, normal, tangent, binormal);
    458                         setInBuffer(this.getMesh(), (s+1)*(s), normal, tangent, binormal);
    459                         setInBuffer(left.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal);
    460                     } else {
    461                         bottomPoint.set(0, bottom.getHeightmapHeight(0,1), 1);
    462 
    463                         averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);
    464                         setInBuffer(this.getMesh(), (s+1)*(s), normal, tangent, binormal);
    465                         setInBuffer(left.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal);
    466                         setInBuffer(bottom.getMesh(), 0, normal, tangent, binormal);
    467 
    468                         if (bottomLeft != null) {
    469                      //       setInBuffer(bottomLeft.getMesh(), s, normal, tangent, binormal);
    470                         }
    471                     }
    472                 } else { // all in the middle
    473                     topPoint.set(0, this.getHeightmapHeight(0,i-1), -1);
    474                     bottomPoint.set(0, this.getHeightmapHeight(0,i+1), 1);
    475 
    476                     averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);
    477                     setInBuffer(this.getMesh(), (s+1)*(i), normal, tangent, binormal);
    478                     setInBuffer(left.getMesh(), (s+1)*(i+1)-1, normal, tangent, binormal);
    479                 }
    480             }
    481         }
    482 
    483         if (top != null) { // top side,    works its way right
    484             for (int i=0; i<s+1; i++) {
    485                 rootPoint.set(0, this.getHeightmapHeight(i,0), 0);
    486                 topPoint.set(0, top.getHeightmapHeight(i,s-1), -1);
    487                 bottomPoint.set(0, this.getHeightmapHeight(i,1), 1);
    488 
    489                 if (i == 0) { // left corner
    490                     // handled by left side pass
    491 
    492                 } else if (i == s) { // right corner
    493 
    494                     // handled by this patch when it does its right side
    495 
    496                 } else { // all in the middle
    497                     leftPoint.set(-1, this.getHeightmapHeight(i-1,0), 0);
    498                     rightPoint.set(1, this.getHeightmapHeight(i+1,0), 0);
    499                     averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);
    500                     setInBuffer(this.getMesh(), i, normal, tangent, binormal);
    501                     setInBuffer(top.getMesh(), (s+1)*(s)+i, normal, tangent, binormal);
    502                 }
    503             }
    504 
    505         }
    506 
    507         if (bottom != null) { // bottom side,    works its way right
    508             for (int i=0; i<s+1; i++) {
    509                 rootPoint.set(0, this.getHeightmapHeight(i,s), 0);
    510                 topPoint.set(0, this.getHeightmapHeight(i,s-1), -1);
    511                 bottomPoint.set(0, bottom.getHeightmapHeight(i,1), 1);
    512 
    513                 if (i == 0) { // left
    514                     // handled by the left side pass
    515 
    516                 } else if (i == s) { // right
    517 
    518                     // handled by the right side pass
    519 
    520                 } else { // all in the middle
    521                     leftPoint.set(-1, this.getHeightmapHeight(i-1,s), 0);
    522                     rightPoint.set(1, this.getHeightmapHeight(i+1,s), 0);
    523                     averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);
    524                     setInBuffer(this.getMesh(), (s+1)*(s)+i, normal, tangent, binormal);
    525                     setInBuffer(bottom.getMesh(), i, normal, tangent, binormal);
    526                 }
    527             }
    528 
    529         }
    530     }
    531 
    532     protected void averageNormalsTangents(
    533             Vector3f topPoint,
    534             Vector3f rootPoint,
    535             Vector3f leftPoint,
    536             Vector3f bottomPoint,
    537             Vector3f rightPoint,
    538             Vector3f normal,
    539             Vector3f tangent,
    540             Vector3f binormal)
    541     {
    542         Vector3f scale = getWorldScale();
    543 
    544         Vector3f n1 = new Vector3f(0,0,0);
    545         if (topPoint != null && leftPoint != null) {
    546             n1.set(calculateNormal(topPoint.mult(scale), rootPoint.mult(scale), leftPoint.mult(scale)));
    547         }
    548         Vector3f n2 = new Vector3f(0,0,0);
    549         if (leftPoint != null && bottomPoint != null) {
    550             n2.set(calculateNormal(leftPoint.mult(scale), rootPoint.mult(scale), bottomPoint.mult(scale)));
    551         }
    552         Vector3f n3 = new Vector3f(0,0,0);
    553         if (rightPoint != null && bottomPoint != null) {
    554             n3.set(calculateNormal(bottomPoint.mult(scale), rootPoint.mult(scale), rightPoint.mult(scale)));
    555         }
    556         Vector3f n4 = new Vector3f(0,0,0);
    557         if (rightPoint != null && topPoint != null) {
    558             n4.set(calculateNormal(rightPoint.mult(scale), rootPoint.mult(scale), topPoint.mult(scale)));
    559         }
    560 
    561         //if (bottomPoint != null && rightPoint != null && rootTex != null && rightTex != null && bottomTex != null)
    562         //    LODGeomap.calculateTangent(new Vector3f[]{rootPoint.mult(scale),rightPoint.mult(scale),bottomPoint.mult(scale)}, new Vector2f[]{rootTex,rightTex,bottomTex}, tangent, binormal);
    563 
    564         normal.set(n1.add(n2).add(n3).add(n4).normalize());
    565 
    566         tangent.set(normal.cross(new Vector3f(0,0,1)).normalize());
    567         binormal.set(new Vector3f(1,0,0).cross(normal).normalize());
    568     }
    569 
    570     private Vector3f calculateNormal(Vector3f firstPoint, Vector3f rootPoint, Vector3f secondPoint) {
    571         Vector3f normal = new Vector3f();
    572         normal.set(firstPoint).subtractLocal(rootPoint)
    573                   .crossLocal(secondPoint.subtract(rootPoint)).normalizeLocal();
    574         return normal;
    575     }
    576 
    577     protected Vector3f getMeshNormal(int x, int z) {
    578         if (x >= size || z >= size)
    579             return null; // out of range
    580 
    581         int index = (z*size+x)*3;
    582         FloatBuffer nb = (FloatBuffer)this.getMesh().getBuffer(Type.Normal).getData();
    583         Vector3f normal = new Vector3f();
    584         normal.x = nb.get(index);
    585         normal.y = nb.get(index+1);
    586         normal.z = nb.get(index+2);
    587         return normal;
    588     }
    589 
    590     /**
    591      * Locks the mesh (sets it static) to improve performance.
    592      * But it it not editable then. Set unlock to make it editable.
    593      */
    594     public void lockMesh() {
    595         getMesh().setStatic();
    596     }
    597 
    598     /**
    599      * Unlocks the mesh (sets it dynamic) to make it editable.
    600      * It will be editable but performance will be reduced.
    601      * Call lockMesh to improve performance.
    602      */
    603     public void unlockMesh() {
    604         getMesh().setDynamic();
    605     }
    606 
    607     /**
    608      * Returns the offset amount this terrain patch uses for textures.
    609      *
    610      * @return The current offset amount.
    611      */
    612     public float getOffsetAmount() {
    613         return offsetAmount;
    614     }
    615 
    616     /**
    617      * Returns the step scale that stretches the height map.
    618      *
    619      * @return The current step scale.
    620      */
    621     public Vector3f getStepScale() {
    622         return stepScale;
    623     }
    624 
    625     /**
    626      * Returns the total size of the terrain.
    627      *
    628      * @return The terrain's total size.
    629      */
    630     public int getTotalSize() {
    631         return totalSize;
    632     }
    633 
    634     /**
    635      * Returns the size of this terrain patch.
    636      *
    637      * @return The current patch size.
    638      */
    639     public int getSize() {
    640         return size;
    641     }
    642 
    643     /**
    644      * Returns the current offset amount. This is used when building texture
    645      * coordinates.
    646      *
    647      * @return The current offset amount.
    648      */
    649     public Vector2f getOffset() {
    650         return offset;
    651     }
    652 
    653     /**
    654      * Sets the value for the current offset amount to use when building texture
    655      * coordinates. Note that this does <b>NOT </b> rebuild the terrain at all.
    656      * This is mostly used for outside constructors of terrain patches.
    657      *
    658      * @param offset
    659      *			The new texture offset.
    660      */
    661     public void setOffset(Vector2f offset) {
    662         this.offset = offset;
    663     }
    664 
    665     /**
    666      * Sets the size of this terrain patch. Note that this does <b>NOT </b>
    667      * rebuild the terrain at all. This is mostly used for outside constructors
    668      * of terrain patches.
    669      *
    670      * @param size
    671      *			The new size.
    672      */
    673     public void setSize(int size) {
    674         this.size = size;
    675 
    676         maxLod = -1; // reset it
    677     }
    678 
    679     /**
    680      * Sets the total size of the terrain . Note that this does <b>NOT </b>
    681      * rebuild the terrain at all. This is mostly used for outside constructors
    682      * of terrain patches.
    683      *
    684      * @param totalSize
    685      *			The new total size.
    686      */
    687     public void setTotalSize(int totalSize) {
    688         this.totalSize = totalSize;
    689     }
    690 
    691     /**
    692      * Sets the step scale of this terrain patch's height map. Note that this
    693      * does <b>NOT </b> rebuild the terrain at all. This is mostly used for
    694      * outside constructors of terrain patches.
    695      *
    696      * @param stepScale
    697      *			The new step scale.
    698      */
    699     public void setStepScale(Vector3f stepScale) {
    700         this.stepScale = stepScale;
    701     }
    702 
    703     /**
    704      * Sets the offset of this terrain texture map. Note that this does <b>NOT
    705      * </b> rebuild the terrain at all. This is mostly used for outside
    706      * constructors of terrain patches.
    707      *
    708      * @param offsetAmount
    709      *			The new texture offset.
    710      */
    711     public void setOffsetAmount(float offsetAmount) {
    712         this.offsetAmount = offsetAmount;
    713     }
    714 
    715     /**
    716      * @return Returns the quadrant.
    717      */
    718     public short getQuadrant() {
    719         return quadrant;
    720     }
    721 
    722     /**
    723      * @param quadrant
    724      *			The quadrant to set.
    725      */
    726     public void setQuadrant(short quadrant) {
    727         this.quadrant = quadrant;
    728     }
    729 
    730     public int getLod() {
    731         return lod;
    732     }
    733 
    734     public void setLod(int lod) {
    735         this.lod = lod;
    736     }
    737 
    738     public int getPreviousLod() {
    739         return previousLod;
    740     }
    741 
    742     public void setPreviousLod(int previousLod) {
    743         this.previousLod = previousLod;
    744     }
    745 
    746     protected int getLodLeft() {
    747         return lodLeft;
    748     }
    749 
    750     protected void setLodLeft(int lodLeft) {
    751         this.lodLeft = lodLeft;
    752     }
    753 
    754     protected int getLodTop() {
    755         return lodTop;
    756     }
    757 
    758     protected void setLodTop(int lodTop) {
    759         this.lodTop = lodTop;
    760     }
    761 
    762     protected int getLodRight() {
    763         return lodRight;
    764     }
    765 
    766     protected void setLodRight(int lodRight) {
    767         this.lodRight = lodRight;
    768     }
    769 
    770     protected int getLodBottom() {
    771         return lodBottom;
    772     }
    773 
    774     protected void setLodBottom(int lodBottom) {
    775         this.lodBottom = lodBottom;
    776     }
    777 
    778     /*public void setLodCalculator(LodCalculatorFactory lodCalculatorFactory) {
    779         this.lodCalculatorFactory = lodCalculatorFactory;
    780         setLodCalculator(lodCalculatorFactory.createCalculator(this));
    781     }*/
    782 
    783     @Override
    784     public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException {
    785         if (refreshFlags != 0)
    786             throw new IllegalStateException("Scene graph must be updated" +
    787                                             " before checking collision");
    788 
    789         if (other instanceof BoundingVolume)
    790             if (!getWorldBound().intersects((BoundingVolume)other))
    791                 return 0;
    792 
    793         if(other instanceof Ray)
    794             return collideWithRay((Ray)other, results);
    795         else if (other instanceof BoundingVolume)
    796             return collideWithBoundingVolume((BoundingVolume)other, results);
    797         else {
    798             throw new UnsupportedCollisionException("TerrainPatch cannnot collide with "+other.getClass().getName());
    799         }
    800     }
    801 
    802 
    803     private int collideWithRay(Ray ray, CollisionResults results) {
    804         // This should be handled in the root terrain quad
    805         return 0;
    806     }
    807 
    808     private int collideWithBoundingVolume(BoundingVolume boundingVolume, CollisionResults results) {
    809         if (boundingVolume instanceof BoundingBox)
    810             return collideWithBoundingBox((BoundingBox)boundingVolume, results);
    811         else if(boundingVolume instanceof BoundingSphere) {
    812             BoundingSphere sphere = (BoundingSphere) boundingVolume;
    813             BoundingBox bbox = new BoundingBox(boundingVolume.getCenter().clone(), sphere.getRadius(),
    814                                                            sphere.getRadius(),
    815                                                            sphere.getRadius());
    816             return collideWithBoundingBox(bbox, results);
    817         }
    818         return 0;
    819     }
    820 
    821     protected Vector3f worldCoordinateToLocal(Vector3f loc) {
    822         Vector3f translated = new Vector3f();
    823         translated.x = loc.x/getWorldScale().x - getWorldTranslation().x;
    824         translated.y = loc.y/getWorldScale().y - getWorldTranslation().y;
    825         translated.z = loc.z/getWorldScale().z - getWorldTranslation().z;
    826         return translated;
    827     }
    828 
    829     /**
    830      * This most definitely is not optimized.
    831      */
    832     private int collideWithBoundingBox(BoundingBox bbox, CollisionResults results) {
    833 
    834         // test the four corners, for cases where the bbox dimensions are less than the terrain grid size, which is probably most of the time
    835         Vector3f topLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x-bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent()));
    836         Vector3f topRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x+bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent()));
    837         Vector3f bottomLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x-bbox.getXExtent(), 0, bbox.getCenter().z+bbox.getZExtent()));
    838         Vector3f bottomRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x+bbox.getXExtent(), 0, bbox.getCenter().z+bbox.getZExtent()));
    839 
    840         Triangle t = getTriangle(topLeft.x, topLeft.z);
    841         if (t != null && bbox.collideWith(t, results) > 0)
    842             return 1;
    843         t = getTriangle(topRight.x, topRight.z);
    844         if (t != null && bbox.collideWith(t, results) > 0)
    845             return 1;
    846         t = getTriangle(bottomLeft.x, bottomLeft.z);
    847         if (t != null && bbox.collideWith(t, results) > 0)
    848             return 1;
    849         t = getTriangle(bottomRight.x, bottomRight.z);
    850         if (t != null && bbox.collideWith(t, results) > 0)
    851             return 1;
    852 
    853         // box is larger than the points on the terrain, so test against the points
    854         for (float z=topLeft.z; z<bottomLeft.z; z+=1) {
    855             for (float x=topLeft.x; x<topRight.x; x+=1) {
    856 
    857                 if (x < 0 || z < 0 || x >= size || z >= size)
    858                     continue;
    859                 t = getTriangle(x,z);
    860                 if (t != null && bbox.collideWith(t, results) > 0)
    861                     return 1;
    862             }
    863         }
    864 
    865         return 0;
    866     }
    867 
    868 
    869     @Override
    870     public void write(JmeExporter ex) throws IOException {
    871         // the mesh is removed, and reloaded when read() is called
    872         // this reduces the save size to 10% by not saving the mesh
    873         Mesh temp = getMesh();
    874         mesh = null;
    875 
    876         super.write(ex);
    877         OutputCapsule oc = ex.getCapsule(this);
    878         oc.write(size, "size", 16);
    879         oc.write(totalSize, "totalSize", 16);
    880         oc.write(quadrant, "quadrant", (short)0);
    881         oc.write(stepScale, "stepScale", Vector3f.UNIT_XYZ);
    882         oc.write(offset, "offset", Vector3f.UNIT_XYZ);
    883         oc.write(offsetAmount, "offsetAmount", 0);
    884         //oc.write(lodCalculator, "lodCalculator", null);
    885         //oc.write(lodCalculatorFactory, "lodCalculatorFactory", null);
    886         oc.write(lodEntropy, "lodEntropy", null);
    887         oc.write(geomap, "geomap", null);
    888 
    889         setMesh(temp);
    890     }
    891 
    892     @Override
    893     public void read(JmeImporter im) throws IOException {
    894         super.read(im);
    895         InputCapsule ic = im.getCapsule(this);
    896         size = ic.readInt("size", 16);
    897         totalSize = ic.readInt("totalSize", 16);
    898         quadrant = ic.readShort("quadrant", (short)0);
    899         stepScale = (Vector3f) ic.readSavable("stepScale", Vector3f.UNIT_XYZ);
    900         offset = (Vector2f) ic.readSavable("offset", Vector3f.UNIT_XYZ);
    901         offsetAmount = ic.readFloat("offsetAmount", 0);
    902         //lodCalculator = (LodCalculator) ic.readSavable("lodCalculator", new DistanceLodCalculator());
    903         //lodCalculator.setTerrainPatch(this);
    904         //lodCalculatorFactory = (LodCalculatorFactory) ic.readSavable("lodCalculatorFactory", null);
    905         lodEntropy = ic.readFloatArray("lodEntropy", null);
    906         geomap = (LODGeomap) ic.readSavable("geomap", null);
    907 
    908         Mesh regen = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false);
    909         setMesh(regen);
    910         //TangentBinormalGenerator.generate(this); // note that this will be removed
    911         ensurePositiveVolumeBBox();
    912     }
    913 
    914     @Override
    915     public TerrainPatch clone() {
    916         TerrainPatch clone = new TerrainPatch();
    917         clone.name = name.toString();
    918         clone.size = size;
    919         clone.totalSize = totalSize;
    920         clone.quadrant = quadrant;
    921         clone.stepScale = stepScale.clone();
    922         clone.offset = offset.clone();
    923         clone.offsetAmount = offsetAmount;
    924         //clone.lodCalculator = lodCalculator.clone();
    925         //clone.lodCalculator.setTerrainPatch(clone);
    926         //clone.setLodCalculator(lodCalculatorFactory.clone());
    927         clone.geomap = new LODGeomap(size, geomap.getHeightArray());
    928         clone.setLocalTranslation(getLocalTranslation().clone());
    929         Mesh m = clone.geomap.createMesh(clone.stepScale, Vector2f.UNIT_XY, clone.offset, clone.offsetAmount, clone.totalSize, false);
    930         clone.setMesh(m);
    931         clone.setMaterial(material.clone());
    932         return clone;
    933     }
    934 
    935     protected void ensurePositiveVolumeBBox() {
    936         if (getModelBound() instanceof BoundingBox) {
    937             if (((BoundingBox)getModelBound()).getYExtent() < 0.001f) {
    938                 // a correction so the box always has a volume
    939                 ((BoundingBox)getModelBound()).setYExtent(0.001f);
    940                 updateWorldBound();
    941             }
    942         }
    943     }
    944 
    945 
    946 
    947 }
    948