blob: 194486a5cebd32cadbf26d9f260796b7279b47ad [file] [log] [blame]
/*
* 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.heightmap;
import com.jme3.math.FastMath;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Creates an heightmap based on the fault algorithm. Each iteration, a random line
* crossing the map is generated. On one side height values are raised, on the other side
* lowered.
* @author cghislai
*/
public class FaultHeightMap extends AbstractHeightMap {
private static final Logger logger = Logger.getLogger(FaultHeightMap.class.getName());
/**
* Values on one side are lowered, on the other side increased,
* creating a step at the fault line
*/
public static final int FAULTTYPE_STEP = 0;
/**
* Values on one side are lowered, then increase lineary while crossing
* the fault line to the other side. The fault line will be a inclined
* plane
*/
public static final int FAULTTYPE_LINEAR = 1;
/**
* Values are lowered on one side, increased on the other, creating a
* cosine curve on the fault line
*/
public static final int FAULTTYPE_COSINE = 2;
/**
* Value are lowered on both side, but increased on the fault line
* creating a smooth ridge on the fault line.
*/
public static final int FAULTTYPE_SINE = 3;
/**
* A linear fault is created
*/
public static final int FAULTSHAPE_LINE = 10;
/**
* A circular fault is created.
*/
public static final int FAULTSHAPE_CIRCLE = 11;
private long seed; // A seed to feed the random
private int iterations; // iterations to perform
private float minFaultHeight; // the height modification applied
private float maxFaultHeight; // the height modification applied
private float minRange; // The range for linear and trigo faults
private float maxRange; // The range for linear and trigo faults
private float minRadius; // radii for circular fault
private float maxRadius;
private int faultType; // The type of fault
private int faultShape; // The type of fault
/**
* Constructor creates the fault. For faulttype other than STEP, a range can
* be provided. For faultshape circle, min and max radii can be provided.
* Don't forget to reload the map if you have set parameters after the constructor
* call.
* @param size The size of the heightmap
* @param iterations Iterations to perform
* @param faultType Type of fault
* @param faultShape Shape of the fault -line or circle
* @param minFaultHeight Height modified on each side
* @param maxFaultHeight Height modified on each side
* @param seed A seed to feed the Random generator
* @see setFaultRange, setMinRadius, setMaxRadius
*/
public FaultHeightMap(int size, int iterations, int faultType, int faultShape, float minFaultHeight, float maxFaultHeight, long seed) throws Exception {
if (size < 0 || iterations < 0) {
throw new Exception("Size and iterations must be greater than 0!");
}
this.size = size;
this.iterations = iterations;
this.faultType = faultType;
this.faultShape = faultShape;
this.minFaultHeight = minFaultHeight;
this.maxFaultHeight = maxFaultHeight;
this.seed = seed;
this.minRange = minFaultHeight;
this.maxRange = maxFaultHeight;
this.minRadius = size / 10;
this.maxRadius = size / 4;
load();
}
/**
* Create an heightmap with linear step faults.
* @param size size of heightmap
* @param iterations number of iterations
* @param minFaultHeight Height modified on each side
* @param maxFaultHeight Height modified on each side
*/
public FaultHeightMap(int size, int iterations, float minFaultHeight, float maxFaultHeight) throws Exception {
this(size, iterations, FAULTTYPE_STEP, FAULTSHAPE_LINE, minFaultHeight, maxFaultHeight, new Random().nextLong());
}
@Override
public boolean load() {
// clean up data if needed.
if (null != heightData) {
unloadHeightMap();
}
heightData = new float[size * size];
float[][] tempBuffer = new float[size][size];
Random random = new Random(seed);
for (int i = 0; i < iterations; i++) {
addFault(tempBuffer, random);
}
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
setHeightAtPoint(tempBuffer[i][j], i, j);
}
}
normalizeTerrain(NORMALIZE_RANGE);
logger.log(Level.INFO, "Fault heightmap generated");
return true;
}
protected void addFault(float[][] tempBuffer, Random random) {
float faultHeight = minFaultHeight + random.nextFloat() * (maxFaultHeight - minFaultHeight);
float range = minRange + random.nextFloat() * (maxRange - minRange);
switch (faultShape) {
case FAULTSHAPE_LINE:
addLineFault(tempBuffer, random, faultHeight, range);
break;
case FAULTSHAPE_CIRCLE:
addCircleFault(tempBuffer, random, faultHeight, range);
break;
}
}
protected void addLineFault(float[][] tempBuffer, Random random, float faultHeight, float range) {
int x1 = random.nextInt(size);
int x2 = random.nextInt(size);
int y1 = random.nextInt(size);
int y2 = random.nextInt(size);
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
float dist = ((x2 - x1) * (j - y1) - (y2 - y1) * (i - x1))
/ (FastMath.sqrt(FastMath.sqr(x2 - x1) + FastMath.sqr(y2 - y1)));
tempBuffer[i][j] += calcHeight(dist, random, faultHeight, range);
}
}
}
protected void addCircleFault(float[][] tempBuffer, Random random, float faultHeight, float range) {
float radius = random.nextFloat() * (maxRadius - minRadius) + minRadius;
int intRadius = (int) FastMath.floor(radius);
// Allox circle center to be out of map if not by more than radius.
// Unlucky cases will put them in the far corner, with the circle
// entirely outside heightmap
int x = random.nextInt(size + 2 * intRadius) - intRadius;
int y = random.nextInt(size + 2 * intRadius) - intRadius;
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
float dist;
if (i != x || j != y) {
int dx = i - x;
int dy = j - y;
float dmag = FastMath.sqrt(FastMath.sqr(dx) + FastMath.sqr(dy));
float rx = x + dx / dmag * radius;
float ry = y + dy / dmag * radius;
dist = FastMath.sign(dmag - radius)
* FastMath.sqrt(FastMath.sqr(i - rx) + FastMath.sqr(j - ry));
} else {
dist = 0;
}
tempBuffer[i][j] += calcHeight(dist, random, faultHeight, range);
}
}
}
protected float calcHeight(float dist, Random random, float faultHeight, float range) {
switch (faultType) {
case FAULTTYPE_STEP: {
return FastMath.sign(dist) * faultHeight;
}
case FAULTTYPE_LINEAR: {
if (FastMath.abs(dist) > range) {
return FastMath.sign(dist) * faultHeight;
}
float f = FastMath.abs(dist) / range;
return FastMath.sign(dist) * faultHeight * f;
}
case FAULTTYPE_SINE: {
if (FastMath.abs(dist) > range) {
return -faultHeight;
}
float f = dist / range;
// We want -1 at f=-1 and f=1; 1 at f=0
return FastMath.sin((1 + 2 * f) * FastMath.PI / 2) * faultHeight;
}
case FAULTTYPE_COSINE: {
if (FastMath.abs(dist) > range) {
return -FastMath.sign(dist) * faultHeight;
}
float f = dist / range;
float val = FastMath.cos((1 + f) * FastMath.PI / 2) * faultHeight;
return val;
}
}
//shoudn't go here
throw new RuntimeException("Code needs update to switch allcases");
}
public int getFaultShape() {
return faultShape;
}
public void setFaultShape(int faultShape) {
this.faultShape = faultShape;
}
public int getFaultType() {
return faultType;
}
public void setFaultType(int faultType) {
this.faultType = faultType;
}
public int getIterations() {
return iterations;
}
public void setIterations(int iterations) {
this.iterations = iterations;
}
public float getMaxFaultHeight() {
return maxFaultHeight;
}
public void setMaxFaultHeight(float maxFaultHeight) {
this.maxFaultHeight = maxFaultHeight;
}
public float getMaxRadius() {
return maxRadius;
}
public void setMaxRadius(float maxRadius) {
this.maxRadius = maxRadius;
}
public float getMaxRange() {
return maxRange;
}
public void setMaxRange(float maxRange) {
this.maxRange = maxRange;
}
public float getMinFaultHeight() {
return minFaultHeight;
}
public void setMinFaultHeight(float minFaultHeight) {
this.minFaultHeight = minFaultHeight;
}
public float getMinRadius() {
return minRadius;
}
public void setMinRadius(float minRadius) {
this.minRadius = minRadius;
}
public float getMinRange() {
return minRange;
}
public void setMinRange(float minRange) {
this.minRange = minRange;
}
public long getSeed() {
return seed;
}
public void setSeed(long seed) {
this.seed = seed;
}
}