You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
350 lines
12 KiB
350 lines
12 KiB
/*
|
|
* Copyright (c) 2009-2018 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() {}
|
|
|
|
public GeoMap(float[] heightData, int width, int height, int maxval){
|
|
this.hdata = heightData;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.maxval = maxval;
|
|
}
|
|
|
|
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
|
|
* @return 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
|
|
* @return 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
|
|
*
|
|
* @return the width of this Geomap
|
|
*/
|
|
public int getWidth() {
|
|
return width;
|
|
}
|
|
|
|
/**
|
|
* Returns the height of this Geomap
|
|
*
|
|
* @return 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
|
|
*
|
|
* @return whether 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
|
|
* @return 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
|
|
*
|
|
* @return 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);
|
|
}
|
|
|
|
|
|
}
|
|
|