Home | History | Annotate | Download | only in terrain
      1 /*
      2  * Copyright (c) 2009-2010 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 package com.jme3.terrain;
     33 
     34 import com.jme3.export.*;
     35 import com.jme3.math.Vector2f;
     36 import com.jme3.math.Vector3f;
     37 import com.jme3.scene.Mesh;
     38 import com.jme3.scene.VertexBuffer.Type;
     39 import com.jme3.util.BufferUtils;
     40 import java.io.IOException;
     41 import java.nio.BufferUnderflowException;
     42 import java.nio.FloatBuffer;
     43 import java.nio.IntBuffer;
     44 
     45 /**
     46  * Constructs heightfields to be used in Terrain.
     47  */
     48 public class GeoMap implements Savable {
     49 
     50     protected float[] hdata;
     51     protected int width, height, maxval;
     52 
     53     public GeoMap() {}
     54 
     55     @Deprecated
     56     public GeoMap(FloatBuffer heightData, int width, int height, int maxval){
     57         hdata = new float[heightData.limit()];
     58         heightData.get(hdata);
     59         this.width = width;
     60         this.height = height;
     61         this.maxval = maxval;
     62     }
     63 
     64     public GeoMap(float[] heightData, int width, int height, int maxval){
     65         this.hdata = heightData;
     66         this.width = width;
     67         this.height = height;
     68         this.maxval = maxval;
     69     }
     70 
     71     @Deprecated
     72     public FloatBuffer getHeightData(){
     73         if (!isLoaded())
     74             return null;
     75         return BufferUtils.createFloatBuffer(hdata);
     76     }
     77 
     78     public float[] getHeightArray(){
     79         if (!isLoaded())
     80             return null;
     81         return hdata;
     82     }
     83 
     84     /**
     85      * @return The maximum possible value that <code>getValue()</code> can
     86      * return. Mostly depends on the source data format (byte, short, int, etc).
     87      */
     88     public int getMaximumValue(){
     89         return maxval;
     90     }
     91 
     92     /**
     93      * Returns the height value for a given point.
     94      *
     95      * MUST return the same value as getHeight(y*getWidth()+x)
     96      *
     97      * @param x the X coordinate
     98      * @param y the Y coordinate
     99      * @returns an arbitrary height looked up from the heightmap
    100      *
    101      * @throws NullPointerException If isLoaded() is false
    102      */
    103     public float getValue(int x, int y) {
    104         return hdata[y*width+x];
    105     }
    106 
    107     /**
    108      * Returns the height value at the given index.
    109      *
    110      * zero index is top left of map,
    111      * getWidth()*getHeight() index is lower right
    112      *
    113      * @param i The index
    114      * @returns an arbitrary height looked up from the heightmap
    115      *
    116      * @throws NullPointerException If isLoaded() is false
    117      */
    118     public float getValue(int i) {
    119         return hdata[i];
    120     }
    121 
    122 
    123     /**
    124      * Returns the width of this Geomap
    125      *
    126      * @returns the width of this Geomap
    127      */
    128     public int getWidth() {
    129         return width;
    130     }
    131 
    132     /**
    133      * Returns the height of this Geomap
    134      *
    135      * @returns the height of this Geomap
    136      */
    137     public int getHeight() {
    138         return height;
    139     }
    140 
    141     /**
    142      * Returns true if the Geomap data is loaded in memory
    143      * If false, then the data is unavailable- must be loaded with load()
    144      * before the methods getHeight/getNormal can be used
    145      *
    146      * @returns wether the geomap data is loaded in system memory
    147      */
    148     public boolean isLoaded() {
    149         return true;
    150     }
    151 
    152     /**
    153      * Creates a normal array from the normal data in this Geomap
    154      *
    155      * @param store A preallocated FloatBuffer where to store the data (optional), size must be >= getWidth()*getHeight()*3
    156      * @returns store, or a new FloatBuffer if store is null
    157      *
    158      * @throws NullPointerException If isLoaded() or hasNormalmap() is false
    159      */
    160     public FloatBuffer writeNormalArray(FloatBuffer store, Vector3f scale) {
    161 
    162         if (store!=null){
    163             if (store.remaining() < getWidth()*getHeight()*3)
    164                 throw new BufferUnderflowException();
    165         }else{
    166             store = BufferUtils.createFloatBuffer(getWidth()*getHeight()*3);
    167         }
    168         store.rewind();
    169 
    170         Vector3f oppositePoint = new Vector3f();
    171         Vector3f adjacentPoint = new Vector3f();
    172         Vector3f rootPoint = new Vector3f();
    173         Vector3f tempNorm = new Vector3f();
    174         int normalIndex = 0;
    175 
    176         for (int y = 0; y < getHeight(); y++) {
    177             for (int x = 0; x < getWidth(); x++) {
    178                 rootPoint.set(x, getValue(x,y), y);
    179                 if (y == getHeight() - 1) {
    180                     if (x == getWidth() - 1) {  // case #4 : last row, last col
    181                         // left cross up
    182 //                            adj = normalIndex - getWidth();
    183 //                            opp = normalIndex - 1;
    184                         adjacentPoint.set(x, getValue(x,y-1), y-1);
    185                         oppositePoint.set(x-1, getValue(x-1, y), y);
    186                     } else {                    // case #3 : last row, except for last col
    187                         // right cross up
    188 //                            adj = normalIndex + 1;
    189 //                            opp = normalIndex - getWidth();
    190                         adjacentPoint.set(x+1, getValue(x+1,y), y);
    191                         oppositePoint.set(x, getValue(x,y-1), y-1);
    192                     }
    193                 } else {
    194                     if (x == getWidth() - 1) {  // case #2 : last column except for last row
    195                         // left cross down
    196                         adjacentPoint.set(x-1, getValue(x-1,y), y);
    197                         oppositePoint.set(x, getValue(x,y+1), y+1);
    198 //                            adj = normalIndex - 1;
    199 //                            opp = normalIndex + getWidth();
    200                     } else {                    // case #1 : most cases
    201                         // right cross down
    202                         adjacentPoint.set(x, getValue(x,y+1), y+1);
    203                         oppositePoint.set(x+1, getValue(x+1,y), y);
    204 //                            adj = normalIndex + getWidth();
    205 //                            opp = normalIndex + 1;
    206                     }
    207                 }
    208 
    209 
    210 
    211                 tempNorm.set(adjacentPoint).subtractLocal(rootPoint)
    212                         .crossLocal(oppositePoint.subtractLocal(rootPoint));
    213                 tempNorm.multLocal(scale).normalizeLocal();
    214 //                    store.put(tempNorm.x).put(tempNorm.y).put(tempNorm.z);
    215                 BufferUtils.setInBuffer(tempNorm, store,
    216                         normalIndex);
    217                 normalIndex++;
    218             }
    219         }
    220 
    221         return store;
    222     }
    223 
    224     /**
    225      * Creates a vertex array from the height data in this Geomap
    226      *
    227      * The scale argument specifies the scale to use for the vertex buffer.
    228      * For example, if scale is 10,1,10, then the greatest X value is getWidth()*10
    229      *
    230      * @param store A preallocated FloatBuffer where to store the data (optional), size must be >= getWidth()*getHeight()*3
    231      * @param scale Created vertexes are scaled by this vector
    232      *
    233      * @returns store, or a new FloatBuffer if store is null
    234      *
    235      * @throws NullPointerException If isLoaded() is false
    236      */
    237     public FloatBuffer writeVertexArray(FloatBuffer store, Vector3f scale, boolean center) {
    238 
    239         if (store!=null){
    240             if (store.remaining() < width*height*3)
    241                 throw new BufferUnderflowException();
    242         }else{
    243             store = BufferUtils.createFloatBuffer(width*height*3);
    244         }
    245 
    246         assert hdata.length == height*width;
    247 
    248         Vector3f offset = new Vector3f(-getWidth() * scale.x * 0.5f,
    249                                        0,
    250                                        -getWidth() * scale.z * 0.5f);
    251         if (!center)
    252             offset.zero();
    253 
    254         int i = 0;
    255         for (int z = 0; z < height; z++){
    256             for (int x = 0; x < width; x++){
    257                 store.put( (float)x*scale.x + offset.x );
    258                 store.put( (float)hdata[i++]*scale.y );
    259                 store.put( (float)z*scale.z + offset.z );
    260             }
    261         }
    262 
    263         return store;
    264     }
    265 
    266     public Vector2f getUV(int x, int y, Vector2f store){
    267         store.set( (float)x / (float)getWidth(),
    268                    (float)y / (float)getHeight() );
    269         return store;
    270     }
    271 
    272     public Vector2f getUV(int i, Vector2f store){
    273         return store;
    274     }
    275 
    276     public FloatBuffer writeTexCoordArray(FloatBuffer store, Vector2f offset, Vector2f scale){
    277         if (store!=null){
    278             if (store.remaining() < getWidth()*getHeight()*2)
    279                 throw new BufferUnderflowException();
    280         }else{
    281             store = BufferUtils.createFloatBuffer(getWidth()*getHeight()*2);
    282         }
    283 
    284         if (offset == null)
    285             offset = new Vector2f();
    286 
    287         Vector2f tcStore = new Vector2f();
    288         for (int y = 0; y < getHeight(); y++){
    289             for (int x = 0; x < getWidth(); x++){
    290                 getUV(x,y,tcStore);
    291                 store.put( offset.x + tcStore.x * scale.x );
    292                 store.put( offset.y + tcStore.y * scale.y );
    293             }
    294 
    295         }
    296 
    297         return store;
    298     }
    299 
    300     public IntBuffer writeIndexArray(IntBuffer store){
    301         int faceN = (getWidth()-1)*(getHeight()-1)*2;
    302 
    303         if (store!=null){
    304             if (store.remaining() < faceN*3)
    305                 throw new BufferUnderflowException();
    306         }else{
    307             store = BufferUtils.createIntBuffer(faceN*3);
    308         }
    309 
    310         int i = 0;
    311         for (int z = 0; z < getHeight()-1; z++){
    312             for (int x = 0; x < getWidth()-1; x++){
    313                 store.put(i).put(i+getWidth()).put(i+getWidth()+1);
    314                 store.put(i+getWidth()+1).put(i+1).put(i);
    315                 i++;
    316 
    317                 // TODO: There's probably a better way to do this..
    318                 if (x==getWidth()-2) i++;
    319             }
    320         }
    321         store.flip();
    322 
    323         return store;
    324     }
    325 
    326     public Mesh createMesh(Vector3f scale, Vector2f tcScale, boolean center){
    327         FloatBuffer pb = writeVertexArray(null, scale, center);
    328         FloatBuffer tb = writeTexCoordArray(null, Vector2f.ZERO, tcScale);
    329         FloatBuffer nb = writeNormalArray(null, scale);
    330         IntBuffer ib = writeIndexArray(null);
    331         Mesh m = new Mesh();
    332         m.setBuffer(Type.Position, 3, pb);
    333         m.setBuffer(Type.Normal, 3, nb);
    334         m.setBuffer(Type.TexCoord, 2, tb);
    335         m.setBuffer(Type.Index, 3, ib);
    336         m.setStatic();
    337         m.updateBound();
    338         return m;
    339     }
    340 
    341     public void write(JmeExporter ex) throws IOException {
    342         OutputCapsule oc = ex.getCapsule(this);
    343         oc.write(hdata, "hdataarray", null);
    344         oc.write(width, "width", 0);
    345         oc.write(height, "height", 0);
    346         oc.write(maxval, "maxval", 0);
    347     }
    348 
    349     public void read(JmeImporter im) throws IOException {
    350         InputCapsule ic = im.getCapsule(this);
    351         hdata = ic.readFloatArray("hdataarray", null);
    352         if (hdata == null) {
    353             FloatBuffer buf = ic.readFloatBuffer("hdata", null);
    354             if (buf != null) {
    355                 hdata = new float[buf.limit()];
    356                 buf.get(hdata);
    357             }
    358         }
    359         width = ic.readInt("width", 0);
    360         height = ic.readInt("height", 0);
    361         maxval = ic.readInt("maxval", 0);
    362     }
    363 
    364 
    365 }
    366