blob: 038dcb2bc2e29d127d7bc261855a1593fd17b86b [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 java.util.Random;
import java.util.logging.Logger;
/**
* <code>HillHeightMap</code> generates a height map base on the Hill
* Algorithm. Terrain is generatd by growing hills of random size and height at
* random points in the heightmap. The terrain is then normalized and valleys
* can be flattened.
*
* @author Frederik Blthoff
* @see <a href="http://www.robot-frog.com/3d/hills/hill.html">Hill Algorithm</a>
*/
public class HillHeightMap extends AbstractHeightMap {
private static final Logger logger = Logger.getLogger(HillHeightMap.class.getName());
private int iterations; // how many hills to generate
private float minRadius; // the minimum size of a hill radius
private float maxRadius; // the maximum size of a hill radius
private long seed; // the seed for the random number generator
/**
* Constructor sets the attributes of the hill system and generates the
* height map.
*
* @param size
* size the size of the terrain to be generated
* @param iterations
* the number of hills to grow
* @param minRadius
* the minimum radius of a hill
* @param maxRadius
* the maximum radius of a hill
* @param seed
* the seed to generate the same heightmap again
* @throws Exception
* @throws JmeException
* if size of the terrain is not greater that zero, or number of
* iterations is not greater that zero
*/
public HillHeightMap(int size, int iterations, float minRadius,
float maxRadius, long seed) throws Exception {
if (size <= 0 || iterations <= 0 || minRadius <= 0 || maxRadius <= 0
|| minRadius >= maxRadius) {
throw new Exception(
"Either size of the terrain is not greater that zero, "
+ "or number of iterations is not greater that zero, "
+ "or minimum or maximum radius are not greater than zero, "
+ "or minimum radius is greater than maximum radius, "
+ "or power of flattening is below one");
}
logger.info("Contructing hill heightmap using seed: " + seed);
this.size = size;
this.seed = seed;
this.iterations = iterations;
this.minRadius = minRadius;
this.maxRadius = maxRadius;
load();
}
/**
* Constructor sets the attributes of the hill system and generates the
* height map by using a random seed.
*
* @param size
* size the size of the terrain to be generated
* @param iterations
* the number of hills to grow
* @param minRadius
* the minimum radius of a hill
* @param maxRadius
* the maximum radius of a hill
* @throws Exception
* @throws JmeException
* if size of the terrain is not greater that zero, or number of
* iterations is not greater that zero
*/
public HillHeightMap(int size, int iterations, float minRadius,
float maxRadius) throws Exception {
this(size, iterations, minRadius, maxRadius, new Random().nextLong());
}
/*
* Generates a heightmap using the Hill Algorithm and the attributes set by
* the constructor or the setters.
*/
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);
// Add the hills
for (int i = 0; i < iterations; i++) {
addHill(tempBuffer, random);
}
// transfer temporary buffer to final heightmap
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
setHeightAtPoint((float) tempBuffer[i][j], j, i);
}
}
normalizeTerrain(NORMALIZE_RANGE);
logger.info("Created Heightmap using the Hill Algorithm");
return true;
}
/**
* Generates a new hill of random size and height at a random position in
* the heightmap. This is the actual Hill algorithm. The <code>Random</code>
* object is used to guarantee the same heightmap for the same seed and
* attributes.
*
* @param tempBuffer
* the temporary height map buffer
* @param random
* the random number generator
*/
protected void addHill(float[][] tempBuffer, Random random) {
// Pick the radius for the hill
float radius = randomRange(random, minRadius, maxRadius);
// Pick a centerpoint for the hill
float x = randomRange(random, -radius, size + radius);
float y = randomRange(random, -radius, size + radius);
float radiusSq = radius * radius;
float distSq;
float height;
// Find the range of hills affected by this hill
int xMin = Math.round(x - radius - 1);
int xMax = Math.round(x + radius + 1);
int yMin = Math.round(y - radius - 1);
int yMax = Math.round(y + radius + 1);
// Don't try to affect points outside the heightmap
if (xMin < 0) {
xMin = 0;
}
if (xMax > size) {
xMax = size - 1;
}
if (yMin < 0) {
yMin = 0;
}
if (yMax > size) {
yMax = size - 1;
}
for (int i = xMin; i <= xMax; i++) {
for (int j = yMin; j <= yMax; j++) {
distSq = (x - i) * (x - i) + (y - j) * (y - j);
height = radiusSq - distSq;
if (height > 0) {
tempBuffer[i][j] += height;
}
}
}
}
private float randomRange(Random random, float min, float max) {
return (random.nextInt() * (max - min) / Integer.MAX_VALUE) + min;
}
/**
* Sets the number of hills to grow. More hills usually mean a nicer
* heightmap.
*
* @param iterations
* the number of hills to grow
* @throws Exception
* @throws JmeException
* if iterations if not greater than zero
*/
public void setIterations(int iterations) throws Exception {
if (iterations <= 0) {
throw new Exception(
"Number of iterations is not greater than zero");
}
this.iterations = iterations;
}
/**
* Sets the minimum radius of a hill.
*
* @param maxRadius
* the maximum radius of a hill
* @throws Exception
* @throws JmeException
* if the maximum radius if not greater than zero or not greater
* than the minimum radius
*/
public void setMaxRadius(float maxRadius) throws Exception {
if (maxRadius <= 0 || maxRadius <= minRadius) {
throw new Exception("The maximum radius is not greater than 0, "
+ "or not greater than the minimum radius");
}
this.maxRadius = maxRadius;
}
/**
* Sets the maximum radius of a hill.
*
* @param minRadius
* the minimum radius of a hill
* @throws Exception
* @throws JmeException if the minimum radius is not greater than zero or not
* lower than the maximum radius
*/
public void setMinRadius(float minRadius) throws Exception {
if (minRadius <= 0 || minRadius >= maxRadius) {
throw new Exception("The minimum radius is not greater than 0, "
+ "or not lower than the maximum radius");
}
this.minRadius = minRadius;
}
}