/*
* 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;
import com.jme3.export.*;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
/**
* Constructs heightfields to be used in Terrain.
*/
public class GeoMap implements Savable {
protected float[] hdata;
protected int width, height, maxval;
public GeoMap() {}
@Deprecated
public GeoMap(FloatBuffer heightData, int width, int height, int maxval){
hdata = new float[heightData.limit()];
heightData.get(hdata);
this.width = width;
this.height = height;
this.maxval = maxval;
}
public GeoMap(float[] heightData, int width, int height, int maxval){
this.hdata = heightData;
this.width = width;
this.height = height;
this.maxval = maxval;
}
@Deprecated
public FloatBuffer getHeightData(){
if (!isLoaded())
return null;
return BufferUtils.createFloatBuffer(hdata);
}
public float[] getHeightArray(){
if (!isLoaded())
return null;
return hdata;
}
/**
* @return The maximum possible value that <code>getValue()</code> can
* return. Mostly depends on the source data format (byte, short, int, etc).
*/
public int getMaximumValue(){
return maxval;
}
/**
* Returns the height value for a given point.
*
* MUST return the same value as getHeight(y*getWidth()+x)
*
* @param x the X coordinate
* @param y the Y coordinate
* @returns an arbitrary height looked up from the heightmap
*
* @throws NullPointerException If isLoaded() is false
*/
public float getValue(int x, int y) {
return hdata[y*width+x];
}
/**
* Returns the height value at the given index.
*
* zero index is top left of map,
* getWidth()*getHeight() index is lower right
*
* @param i The index
* @returns an arbitrary height looked up from the heightmap
*
* @throws NullPointerException If isLoaded() is false
*/
public float getValue(int i) {
return hdata[i];
}
/**
* Returns the width of this Geomap
*
* @returns the width of this Geomap
*/
public int getWidth() {
return width;
}
/**
* Returns the height of this Geomap
*
* @returns the height of this Geomap
*/
public int getHeight() {
return height;
}
/**
* Returns true if the Geomap data is loaded in memory
* If false, then the data is unavailable- must be loaded with load()
* before the methods getHeight/getNormal can be used
*
* @returns wether the geomap data is loaded in system memory
*/
public boolean isLoaded() {
return true;
}
/**
* Creates a normal array from the normal data in this Geomap
*
* @param store A preallocated FloatBuffer where to store the data (optional), size must be >= getWidth()*getHeight()*3
* @returns store, or a new FloatBuffer if store is null
*
* @throws NullPointerException If isLoaded() or hasNormalmap() is false
*/
public FloatBuffer writeNormalArray(FloatBuffer store, Vector3f scale) {
if (store!=null){
if (store.remaining() < getWidth()*getHeight()*3)
throw new BufferUnderflowException();
}else{
store = BufferUtils.createFloatBuffer(getWidth()*getHeight()*3);
}
store.rewind();
Vector3f oppositePoint = new Vector3f();
Vector3f adjacentPoint = new Vector3f();
Vector3f rootPoint = new Vector3f();
Vector3f tempNorm = new Vector3f();
int normalIndex = 0;
for (int y = 0; y < getHeight(); y++) {
for (int x = 0; x < getWidth(); x++) {
rootPoint.set(x, getValue(x,y), y);
if (y == getHeight() - 1) {
if (x == getWidth() - 1) { // case #4 : last row, last col
// left cross up
// adj = normalIndex - getWidth();
// opp = normalIndex - 1;
adjacentPoint.set(x, getValue(x,y-1), y-1);
oppositePoint.set(x-1, getValue(x-1, y), y);
} else { // case #3 : last row, except for last col
// right cross up
// adj = normalIndex + 1;
// opp = normalIndex - getWidth();
adjacentPoint.set(x+1, getValue(x+1,y), y);
oppositePoint.set(x, getValue(x,y-1), y-1);
}
} else {
if (x == getWidth() - 1) { // case #2 : last column except for last row
// left cross down
adjacentPoint.set(x-1, getValue(x-1,y), y);
oppositePoint.set(x, getValue(x,y+1), y+1);
// adj = normalIndex - 1;
// opp = normalIndex + getWidth();
} else { // case #1 : most cases
// right cross down
adjacentPoint.set(x, getValue(x,y+1), y+1);
oppositePoint.set(x+1, getValue(x+1,y), y);
// adj = normalIndex + getWidth();
// opp = normalIndex + 1;
}
}
tempNorm.set(adjacentPoint).subtractLocal(rootPoint)
.crossLocal(oppositePoint.subtractLocal(rootPoint));
tempNorm.multLocal(scale).normalizeLocal();
// store.put(tempNorm.x).put(tempNorm.y).put(tempNorm.z);
BufferUtils.setInBuffer(tempNorm, store,
normalIndex);
normalIndex++;
}
}
return store;
}
/**
* Creates a vertex array from the height data in this Geomap
*
* The scale argument specifies the scale to use for the vertex buffer.
* For example, if scale is 10,1,10, then the greatest X value is getWidth()*10
*
* @param store A preallocated FloatBuffer where to store the data (optional), size must be >= getWidth()*getHeight()*3
* @param scale Created vertexes are scaled by this vector
*
* @returns store, or a new FloatBuffer if store is null
*
* @throws NullPointerException If isLoaded() is false
*/
public FloatBuffer writeVertexArray(FloatBuffer store, Vector3f scale, boolean center) {
if (store!=null){
if (store.remaining() < width*height*3)
throw new BufferUnderflowException();
}else{
store = BufferUtils.createFloatBuffer(width*height*3);
}
assert hdata.length == height*width;
Vector3f offset = new Vector3f(-getWidth() * scale.x * 0.5f,
0,
-getWidth() * scale.z * 0.5f);
if (!center)
offset.zero();
int i = 0;
for (int z = 0; z < height; z++){
for (int x = 0; x < width; x++){
store.put( (float)x*scale.x + offset.x );
store.put( (float)hdata[i++]*scale.y );
store.put( (float)z*scale.z + offset.z );
}
}
return store;
}
public Vector2f getUV(int x, int y, Vector2f store){
store.set( (float)x / (float)getWidth(),
(float)y / (float)getHeight() );
return store;
}
public Vector2f getUV(int i, Vector2f store){
return store;
}
public FloatBuffer writeTexCoordArray(FloatBuffer store, Vector2f offset, Vector2f scale){
if (store!=null){
if (store.remaining() < getWidth()*getHeight()*2)
throw new BufferUnderflowException();
}else{
store = BufferUtils.createFloatBuffer(getWidth()*getHeight()*2);
}
if (offset == null)
offset = new Vector2f();
Vector2f tcStore = new Vector2f();
for (int y = 0; y < getHeight(); y++){
for (int x = 0; x < getWidth(); x++){
getUV(x,y,tcStore);
store.put( offset.x + tcStore.x * scale.x );
store.put( offset.y + tcStore.y * scale.y );
}
}
return store;
}
public IntBuffer writeIndexArray(IntBuffer store){
int faceN = (getWidth()-1)*(getHeight()-1)*2;
if (store!=null){
if (store.remaining() < faceN*3)
throw new BufferUnderflowException();
}else{
store = BufferUtils.createIntBuffer(faceN*3);
}
int i = 0;
for (int z = 0; z < getHeight()-1; z++){
for (int x = 0; x < getWidth()-1; x++){
store.put(i).put(i+getWidth()).put(i+getWidth()+1);
store.put(i+getWidth()+1).put(i+1).put(i);
i++;
// TODO: There's probably a better way to do this..
if (x==getWidth()-2) i++;
}
}
store.flip();
return store;
}
public Mesh createMesh(Vector3f scale, Vector2f tcScale, boolean center){
FloatBuffer pb = writeVertexArray(null, scale, center);
FloatBuffer tb = writeTexCoordArray(null, Vector2f.ZERO, tcScale);
FloatBuffer nb = writeNormalArray(null, scale);
IntBuffer ib = writeIndexArray(null);
Mesh m = new Mesh();
m.setBuffer(Type.Position, 3, pb);
m.setBuffer(Type.Normal, 3, nb);
m.setBuffer(Type.TexCoord, 2, tb);
m.setBuffer(Type.Index, 3, ib);
m.setStatic();
m.updateBound();
return m;
}
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.write(hdata, "hdataarray", null);
oc.write(width, "width", 0);
oc.write(height, "height", 0);
oc.write(maxval, "maxval", 0);
}
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
hdata = ic.readFloatArray("hdataarray", null);
if (hdata == null) {
FloatBuffer buf = ic.readFloatBuffer("hdata", null);
if (buf != null) {
hdata = new float[buf.limit()];
buf.get(hdata);
}
}
width = ic.readInt("width", 0);
height = ic.readInt("height", 0);
maxval = ic.readInt("maxval", 0);
}
}