/* * 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.FINE, "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; } }