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 GeoMap()53 public GeoMap() {} 54 55 @Deprecated GeoMap(FloatBuffer heightData, int width, int height, int maxval)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 GeoMap(float[] heightData, int width, int height, int maxval)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 getHeightData()72 public FloatBuffer getHeightData(){ 73 if (!isLoaded()) 74 return null; 75 return BufferUtils.createFloatBuffer(hdata); 76 } 77 getHeightArray()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 */ getMaximumValue()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 */ getValue(int x, int y)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 */ getValue(int i)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 */ getWidth()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 */ getHeight()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 */ isLoaded()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 */ writeNormalArray(FloatBuffer store, Vector3f scale)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 */ writeVertexArray(FloatBuffer store, Vector3f scale, boolean center)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 getUV(int x, int y, Vector2f store)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 getUV(int i, Vector2f store)272 public Vector2f getUV(int i, Vector2f store){ 273 return store; 274 } 275 writeTexCoordArray(FloatBuffer store, Vector2f offset, Vector2f scale)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 writeIndexArray(IntBuffer store)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 createMesh(Vector3f scale, Vector2f tcScale, boolean center)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 write(JmeExporter ex)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 read(JmeImporter im)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