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.
273 lines
11 KiB
273 lines
11 KiB
/*
|
|
* 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;
|
|
import javax.management.JMException;
|
|
|
|
/**
|
|
* <code>MidpointDisplacementHeightMap</code> generates an heightmap based on
|
|
* the midpoint displacement algorithm. See Constructor javadoc for more info.
|
|
* @author cghislai
|
|
*/
|
|
public class MidpointDisplacementHeightMap extends AbstractHeightMap {
|
|
|
|
private static final Logger logger = Logger.getLogger(MidpointDisplacementHeightMap.class.getName());
|
|
private float range; // The offset in which randomness will be added
|
|
private float persistence; // How the random offset evolves with increasing passes
|
|
private long seed; // seed for random number generator
|
|
|
|
/**
|
|
* The constructor generates the heightmap. After the first 4 corners are
|
|
* randomly given an height, the center will be heighted to the average of
|
|
* the 4 corners to which a random value is added. Then other passes fill
|
|
* the heightmap by the same principle.
|
|
* The random value is generated between the values <code>-range</code>
|
|
* and <code>range</code>. The <code>range</code> parameter is multiplied by
|
|
* the <code>persistence</code> parameter each pass to smoothen close cell heights.
|
|
* Extends this class and override the getOffset function for more control of
|
|
* the randomness (you can use the coordinates and/or the computed average height
|
|
* to influence the random amount added.
|
|
*
|
|
* @param size
|
|
* The size of the heightmap, must be 2^N+1
|
|
* @param range
|
|
* The range in which randomness will be added. A value of 1 will
|
|
* allow -1 to 1 value changes.
|
|
* @param persistence
|
|
* The factor by which the range will evolve at each iteration.
|
|
* A value of 0.5f will halve the range at each iteration and is
|
|
* typically a good choice
|
|
* @param seed
|
|
* A seed to feed the random number generator.
|
|
* @throw JMException if size is not a power of two plus one.
|
|
*/
|
|
public MidpointDisplacementHeightMap(int size, float range, float persistence, long seed) throws Exception {
|
|
if (size < 0 || !FastMath.isPowerOfTwo(size - 1)) {
|
|
throw new JMException("The size is negative or not of the form 2^N +1"
|
|
+ " (a power of two plus one)");
|
|
}
|
|
this.size = size;
|
|
this.range = range;
|
|
this.persistence = persistence;
|
|
this.seed = seed;
|
|
load();
|
|
}
|
|
|
|
/**
|
|
* The constructor generates the heightmap. After the first 4 corners are
|
|
* randomly given an height, the center will be heighted to the average of
|
|
* the 4 corners to which a random value is added. Then other passes fill
|
|
* the heightmap by the same principle.
|
|
* The random value is generated between the values <code>-range</code>
|
|
* and <code>range</code>. The <code>range</code> parameter is multiplied by
|
|
* the <code>persistence</code> parameter each pass to smoothen close cell heights.
|
|
* @param size
|
|
* The size of the heightmap, must be 2^N+1
|
|
* @param range
|
|
* The range in which randomness will be added. A value of 1 will
|
|
* allow -1 to 1 value changes.
|
|
* @param persistence
|
|
* The factor by which the range will evolve at each iteration.
|
|
* A value of 0.5f will halve the range at each iteration and is
|
|
* typically a good choice
|
|
* @throw JMException if size is not a power of two plus one.
|
|
*/
|
|
public MidpointDisplacementHeightMap(int size, float range, float persistence) throws Exception {
|
|
this(size, range, persistence, new Random().nextLong());
|
|
}
|
|
|
|
/**
|
|
* Generate the heightmap.
|
|
* @return
|
|
*/
|
|
@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);
|
|
|
|
tempBuffer[0][0] = random.nextFloat();
|
|
tempBuffer[0][size - 1] = random.nextFloat();
|
|
tempBuffer[size - 1][0] = random.nextFloat();
|
|
tempBuffer[size - 1][size - 1] = random.nextFloat();
|
|
|
|
float offsetRange = range;
|
|
int stepSize = size - 1;
|
|
while (stepSize > 1) {
|
|
int[] nextCoords = {0, 0};
|
|
while (nextCoords != null) {
|
|
nextCoords = doSquareStep(tempBuffer, nextCoords, stepSize, offsetRange, random);
|
|
}
|
|
nextCoords = new int[]{0, 0};
|
|
while (nextCoords != null) {
|
|
nextCoords = doDiamondStep(tempBuffer, nextCoords, stepSize, offsetRange, random);
|
|
}
|
|
stepSize /= 2;
|
|
offsetRange *= persistence;
|
|
}
|
|
|
|
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.log(Level.FINE, "Midpoint displacement heightmap generated");
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Will fill the value at (coords[0]+stepSize/2, coords[1]+stepSize/2) with
|
|
* the average from the corners of the square with topleft corner at (coords[0],coords[1])
|
|
* and width of stepSize.
|
|
* @param tempBuffer the temprary heightmap
|
|
* @param coords an int array of lenght 2 with the x coord in position 0
|
|
* @param stepSize the size of the square
|
|
* @param offsetRange the offset range within a random value is picked and added to the average
|
|
* @param random the random generator
|
|
* @return
|
|
*/
|
|
protected int[] doSquareStep(float[][] tempBuffer, int[] coords, int stepSize, float offsetRange, Random random) {
|
|
float cornerAverage = 0;
|
|
int x = coords[0];
|
|
int y = coords[1];
|
|
cornerAverage += tempBuffer[x][y];
|
|
cornerAverage += tempBuffer[x + stepSize][y];
|
|
cornerAverage += tempBuffer[x + stepSize][y + stepSize];
|
|
cornerAverage += tempBuffer[x][y + stepSize];
|
|
cornerAverage /= 4;
|
|
float offset = getOffset(random, offsetRange, coords, cornerAverage);
|
|
tempBuffer[x + stepSize / 2][y + stepSize / 2] = cornerAverage + offset;
|
|
|
|
// Only get to next square if the center is still in map
|
|
if (x + stepSize * 3 / 2 < size) {
|
|
return new int[]{x + stepSize, y};
|
|
}
|
|
if (y + stepSize * 3 / 2 < size) {
|
|
return new int[]{0, y + stepSize};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Will fill the cell at (x+stepSize/2, y) with the average of the 4 corners
|
|
* of the diamond centered on that point with width and height of stepSize.
|
|
* @param tempBuffer
|
|
* @param coords
|
|
* @param stepSize
|
|
* @param offsetRange
|
|
* @param random
|
|
* @return
|
|
*/
|
|
protected int[] doDiamondStep(float[][] tempBuffer, int[] coords, int stepSize, float offsetRange, Random random) {
|
|
int cornerNbr = 0;
|
|
float cornerAverage = 0;
|
|
int x = coords[0];
|
|
int y = coords[1];
|
|
int[] dxs = new int[]{0, stepSize / 2, stepSize, stepSize / 2};
|
|
int[] dys = new int[]{0, -stepSize / 2, 0, stepSize / 2};
|
|
|
|
for (int d = 0; d < 4; d++) {
|
|
int i = x + dxs[d];
|
|
if (i < 0 || i > size - 1) {
|
|
continue;
|
|
}
|
|
int j = y + dys[d];
|
|
if (j < 0 || j > size - 1) {
|
|
continue;
|
|
}
|
|
cornerAverage += tempBuffer[i][j];
|
|
cornerNbr++;
|
|
}
|
|
cornerAverage /= cornerNbr;
|
|
float offset = getOffset(random, offsetRange, coords, cornerAverage);
|
|
tempBuffer[x + stepSize / 2][y] = cornerAverage + offset;
|
|
|
|
if (x + stepSize * 3 / 2 < size) {
|
|
return new int[]{x + stepSize, y};
|
|
}
|
|
if (y + stepSize / 2 < size) {
|
|
if (x + stepSize == size - 1) {
|
|
return new int[]{-stepSize / 2, y + stepSize / 2};
|
|
} else {
|
|
return new int[]{0, y + stepSize / 2};
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Generate a random value to add to the computed average
|
|
* @param random the random generator
|
|
* @param offsetRange
|
|
* @param coords
|
|
* @param average
|
|
* @return A semi-random value within offsetRange
|
|
*/
|
|
protected float getOffset(Random random, float offsetRange, int[] coords, float average) {
|
|
return 2 * (random.nextFloat() - 0.5F) * offsetRange;
|
|
}
|
|
|
|
public float getPersistence() {
|
|
return persistence;
|
|
}
|
|
|
|
public void setPersistence(float persistence) {
|
|
this.persistence = persistence;
|
|
}
|
|
|
|
public float getRange() {
|
|
return range;
|
|
}
|
|
|
|
public void setRange(float range) {
|
|
this.range = range;
|
|
}
|
|
|
|
public long getSeed() {
|
|
return seed;
|
|
}
|
|
|
|
public void setSeed(long seed) {
|
|
this.seed = seed;
|
|
}
|
|
}
|
|
|