1/* 2 * Copyright (c) 2009-2012 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32package com.jme3.terrain.heightmap; 33 34import java.util.Random; 35import java.util.logging.Logger; 36 37/** 38 * <code>HillHeightMap</code> generates a height map base on the Hill 39 * Algorithm. Terrain is generatd by growing hills of random size and height at 40 * random points in the heightmap. The terrain is then normalized and valleys 41 * can be flattened. 42 * 43 * @author Frederik Blthoff 44 * @see <a href="http://www.robot-frog.com/3d/hills/hill.html">Hill Algorithm</a> 45 */ 46public class HillHeightMap extends AbstractHeightMap { 47 48 private static final Logger logger = Logger.getLogger(HillHeightMap.class.getName()); 49 private int iterations; // how many hills to generate 50 private float minRadius; // the minimum size of a hill radius 51 private float maxRadius; // the maximum size of a hill radius 52 private long seed; // the seed for the random number generator 53 54 /** 55 * Constructor sets the attributes of the hill system and generates the 56 * height map. 57 * 58 * @param size 59 * size the size of the terrain to be generated 60 * @param iterations 61 * the number of hills to grow 62 * @param minRadius 63 * the minimum radius of a hill 64 * @param maxRadius 65 * the maximum radius of a hill 66 * @param seed 67 * the seed to generate the same heightmap again 68 * @throws Exception 69 * @throws JmeException 70 * if size of the terrain is not greater that zero, or number of 71 * iterations is not greater that zero 72 */ 73 public HillHeightMap(int size, int iterations, float minRadius, 74 float maxRadius, long seed) throws Exception { 75 if (size <= 0 || iterations <= 0 || minRadius <= 0 || maxRadius <= 0 76 || minRadius >= maxRadius) { 77 throw new Exception( 78 "Either size of the terrain is not greater that zero, " 79 + "or number of iterations is not greater that zero, " 80 + "or minimum or maximum radius are not greater than zero, " 81 + "or minimum radius is greater than maximum radius, " 82 + "or power of flattening is below one"); 83 } 84 logger.info("Contructing hill heightmap using seed: " + seed); 85 this.size = size; 86 this.seed = seed; 87 this.iterations = iterations; 88 this.minRadius = minRadius; 89 this.maxRadius = maxRadius; 90 91 load(); 92 } 93 94 /** 95 * Constructor sets the attributes of the hill system and generates the 96 * height map by using a random seed. 97 * 98 * @param size 99 * size the size of the terrain to be generated 100 * @param iterations 101 * the number of hills to grow 102 * @param minRadius 103 * the minimum radius of a hill 104 * @param maxRadius 105 * the maximum radius of a hill 106 * @throws Exception 107 * @throws JmeException 108 * if size of the terrain is not greater that zero, or number of 109 * iterations is not greater that zero 110 */ 111 public HillHeightMap(int size, int iterations, float minRadius, 112 float maxRadius) throws Exception { 113 this(size, iterations, minRadius, maxRadius, new Random().nextLong()); 114 } 115 116 /* 117 * Generates a heightmap using the Hill Algorithm and the attributes set by 118 * the constructor or the setters. 119 */ 120 public boolean load() { 121 // clean up data if needed. 122 if (null != heightData) { 123 unloadHeightMap(); 124 } 125 heightData = new float[size * size]; 126 float[][] tempBuffer = new float[size][size]; 127 Random random = new Random(seed); 128 129 // Add the hills 130 for (int i = 0; i < iterations; i++) { 131 addHill(tempBuffer, random); 132 } 133 134 // transfer temporary buffer to final heightmap 135 for (int i = 0; i < size; i++) { 136 for (int j = 0; j < size; j++) { 137 setHeightAtPoint((float) tempBuffer[i][j], j, i); 138 } 139 } 140 141 normalizeTerrain(NORMALIZE_RANGE); 142 143 logger.info("Created Heightmap using the Hill Algorithm"); 144 145 return true; 146 } 147 148 /** 149 * Generates a new hill of random size and height at a random position in 150 * the heightmap. This is the actual Hill algorithm. The <code>Random</code> 151 * object is used to guarantee the same heightmap for the same seed and 152 * attributes. 153 * 154 * @param tempBuffer 155 * the temporary height map buffer 156 * @param random 157 * the random number generator 158 */ 159 protected void addHill(float[][] tempBuffer, Random random) { 160 // Pick the radius for the hill 161 float radius = randomRange(random, minRadius, maxRadius); 162 163 // Pick a centerpoint for the hill 164 float x = randomRange(random, -radius, size + radius); 165 float y = randomRange(random, -radius, size + radius); 166 167 float radiusSq = radius * radius; 168 float distSq; 169 float height; 170 171 // Find the range of hills affected by this hill 172 int xMin = Math.round(x - radius - 1); 173 int xMax = Math.round(x + radius + 1); 174 175 int yMin = Math.round(y - radius - 1); 176 int yMax = Math.round(y + radius + 1); 177 178 // Don't try to affect points outside the heightmap 179 if (xMin < 0) { 180 xMin = 0; 181 } 182 if (xMax > size) { 183 xMax = size - 1; 184 } 185 186 if (yMin < 0) { 187 yMin = 0; 188 } 189 if (yMax > size) { 190 yMax = size - 1; 191 } 192 193 for (int i = xMin; i <= xMax; i++) { 194 for (int j = yMin; j <= yMax; j++) { 195 distSq = (x - i) * (x - i) + (y - j) * (y - j); 196 height = radiusSq - distSq; 197 198 if (height > 0) { 199 tempBuffer[i][j] += height; 200 } 201 } 202 } 203 } 204 205 private float randomRange(Random random, float min, float max) { 206 return (random.nextInt() * (max - min) / Integer.MAX_VALUE) + min; 207 } 208 209 /** 210 * Sets the number of hills to grow. More hills usually mean a nicer 211 * heightmap. 212 * 213 * @param iterations 214 * the number of hills to grow 215 * @throws Exception 216 * @throws JmeException 217 * if iterations if not greater than zero 218 */ 219 public void setIterations(int iterations) throws Exception { 220 if (iterations <= 0) { 221 throw new Exception( 222 "Number of iterations is not greater than zero"); 223 } 224 this.iterations = iterations; 225 } 226 227 /** 228 * Sets the minimum radius of a hill. 229 * 230 * @param maxRadius 231 * the maximum radius of a hill 232 * @throws Exception 233 * @throws JmeException 234 * if the maximum radius if not greater than zero or not greater 235 * than the minimum radius 236 */ 237 public void setMaxRadius(float maxRadius) throws Exception { 238 if (maxRadius <= 0 || maxRadius <= minRadius) { 239 throw new Exception("The maximum radius is not greater than 0, " 240 + "or not greater than the minimum radius"); 241 } 242 this.maxRadius = maxRadius; 243 } 244 245 /** 246 * Sets the maximum radius of a hill. 247 * 248 * @param minRadius 249 * the minimum radius of a hill 250 * @throws Exception 251 * @throws JmeException if the minimum radius is not greater than zero or not 252 * lower than the maximum radius 253 */ 254 public void setMinRadius(float minRadius) throws Exception { 255 if (minRadius <= 0 || minRadius >= maxRadius) { 256 throw new Exception("The minimum radius is not greater than 0, " 257 + "or not lower than the maximum radius"); 258 } 259 this.minRadius = minRadius; 260 } 261} 262