1/* 2 * Copyright (c) 2009-2010 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>FluidSimHeightMap</code> generates a height map based using some 39 * sort of fluid simulation. The heightmap is treated as a highly viscous and 40 * rubbery fluid enabling to fine tune the generated heightmap using a number 41 * of parameters. 42 * 43 * @author Frederik Boelthoff 44 * @see <a href="http://www.gamedev.net/reference/articles/article2001.asp">Terrain Generation Using Fluid Simulation</a> 45 * @version $Id$ 46 * 47 */ 48public class FluidSimHeightMap extends AbstractHeightMap { 49 50 private static final Logger logger = Logger.getLogger(FluidSimHeightMap.class.getName()); 51 private float waveSpeed = 100.0f; // speed at which the waves travel 52 private float timeStep = 0.033f; // constant time-step between each iteration 53 private float nodeDistance = 10.0f; // distance between each node of the surface 54 private float viscosity = 100.0f; // viscosity of the fluid 55 private int iterations; // number of iterations 56 private float minInitialHeight = -500; // min initial height 57 private float maxInitialHeight = 500; // max initial height 58 private long seed; // the seed for the random number generator 59 float coefA, coefB, coefC; // pre-computed coefficients in the fluid equation 60 61 /** 62 * Constructor sets the attributes of the hill system and generates the 63 * height map. It gets passed a number of tweakable parameters which 64 * fine-tune the outcome. 65 * 66 * @param size 67 * size the size of the terrain to be generated 68 * @param iterations 69 * the number of iterations to do 70 * @param minInitialHeight 71 * the minimum initial height of a terrain value 72 * @param maxInitialHeight 73 * the maximum initial height of a terrain value 74 * @param viscosity 75 * the viscosity of the fluid 76 * @param waveSpeed 77 * the speed at which the waves travel 78 * @param timestep 79 * the constant time-step between each iteration 80 * @param nodeDistance 81 * the distance between each node of the heightmap 82 * @param seed 83 * the seed to generate the same heightmap again 84 * @throws JmeException 85 * if size of the terrain is not greater that zero, or number of 86 * iterations is not greater that zero, or the minimum initial height 87 * is greater than the maximum (or the other way around) 88 */ 89 public FluidSimHeightMap(int size, int iterations, float minInitialHeight, float maxInitialHeight, float viscosity, float waveSpeed, float timestep, float nodeDistance, long seed) throws Exception { 90 if (size <= 0 || iterations <= 0 || minInitialHeight >= maxInitialHeight) { 91 throw new Exception( 92 "Either size of the terrain is not greater that zero, " 93 + "or number of iterations is not greater that zero, " 94 + "or minimum height greater or equal as the maximum, " 95 + "or maximum height smaller or equal as the minimum."); 96 } 97 98 this.size = size; 99 this.seed = seed; 100 this.iterations = iterations; 101 this.minInitialHeight = minInitialHeight; 102 this.maxInitialHeight = maxInitialHeight; 103 this.viscosity = viscosity; 104 this.waveSpeed = waveSpeed; 105 this.timeStep = timestep; 106 this.nodeDistance = nodeDistance; 107 108 load(); 109 } 110 111 /** 112 * Constructor sets the attributes of the hill system and generates the 113 * height map. 114 * 115 * @param size 116 * size the size of the terrain to be generated 117 * @param iterations 118 * the number of iterations to do 119 * @throws JmeException 120 * if size of the terrain is not greater that zero, or number of 121 * iterations is not greater that zero 122 */ 123 public FluidSimHeightMap(int size, int iterations) throws Exception { 124 if (size <= 0 || iterations <= 0) { 125 throw new Exception( 126 "Either size of the terrain is not greater that zero, " 127 + "or number of iterations is not greater that zero"); 128 } 129 130 this.size = size; 131 this.iterations = iterations; 132 133 load(); 134 } 135 136 137 /* 138 * Generates a heightmap using fluid simulation and the attributes set by 139 * the constructor or the setters. 140 */ 141 public boolean load() { 142 // Clean up data if needed. 143 if (null != heightData) { 144 unloadHeightMap(); 145 } 146 147 heightData = new float[size * size]; 148 float[][] tempBuffer = new float[2][size * size]; 149 Random random = new Random(seed); 150 151 // pre-compute the coefficients in the fluid equation 152 coefA = (4 - (8 * waveSpeed * waveSpeed * timeStep * timeStep) / (nodeDistance * nodeDistance)) / (viscosity * timeStep + 2); 153 coefB = (viscosity * timeStep - 2) / (viscosity * timeStep + 2); 154 coefC = ((2 * waveSpeed * waveSpeed * timeStep * timeStep) / (nodeDistance * nodeDistance)) / (viscosity * timeStep + 2); 155 156 // initialize the heightmaps to random values except for the edges 157 for (int i = 0; i < size; i++) { 158 for (int j = 0; j < size; j++) { 159 tempBuffer[0][j + i * size] = tempBuffer[1][j + i * size] = randomRange(random, minInitialHeight, maxInitialHeight); 160 } 161 } 162 163 int curBuf = 0; 164 int ind; 165 166 float[] oldBuffer; 167 float[] newBuffer; 168 169 // Iterate over the heightmap, applying the fluid simulation equation. 170 // Although it requires knowledge of the two previous timesteps, it only 171 // accesses one pixel of the k-1 timestep, so using a simple trick we only 172 // need to store the heightmap twice, not three times, and we can avoid 173 // copying data every iteration. 174 for (int i = 0; i < iterations; i++) { 175 oldBuffer = tempBuffer[1 - curBuf]; 176 newBuffer = tempBuffer[curBuf]; 177 178 for (int y = 0; y < size; y++) { 179 for (int x = 0; x < size; x++) { 180 ind = x + y * size; 181 float neighborsValue = 0; 182 int neighbors = 0; 183 184 if (x > 0) { 185 neighborsValue += newBuffer[ind - 1]; 186 neighbors++; 187 } 188 if (x < size - 1) { 189 neighborsValue += newBuffer[ind + 1]; 190 neighbors++; 191 } 192 if (y > 0) { 193 neighborsValue += newBuffer[ind - size]; 194 neighbors++; 195 } 196 if (y < size - 1) { 197 neighborsValue += newBuffer[ind + size]; 198 neighbors++; 199 } 200 if (neighbors != 4) { 201 neighborsValue *= 4 / neighbors; 202 } 203 oldBuffer[ind] = coefA * newBuffer[ind] + coefB 204 * oldBuffer[ind] + coefC * (neighborsValue); 205 } 206 } 207 208 curBuf = 1 - curBuf; 209 } 210 211 // put the normalized heightmap into the range [0...255] and into the heightmap 212 for (int y = 0; y < size; y++) { 213 for (int x = 0; x < size; x++) { 214 heightData[x + y * size] = (float) (tempBuffer[curBuf][x + y * size]); 215 } 216 } 217 normalizeTerrain(NORMALIZE_RANGE); 218 219 logger.info("Created Heightmap using fluid simulation"); 220 221 return true; 222 } 223 224 private float randomRange(Random random, float min, float max) { 225 return (random.nextFloat() * (max - min)) + min; 226 } 227 228 /** 229 * Sets the number of times the fluid simulation should be iterated over 230 * the heightmap. The more often this is, the less features (hills, etc) 231 * the terrain will have, and the smoother it will be. 232 * 233 * @param iterations 234 * the number of iterations to do 235 * @throws JmeException 236 * if iterations if not greater than zero 237 */ 238 public void setIterations(int iterations) throws Exception { 239 if (iterations <= 0) { 240 throw new Exception( 241 "Number of iterations is not greater than zero"); 242 } 243 this.iterations = iterations; 244 } 245 246 /** 247 * Sets the maximum initial height of the terrain. 248 * 249 * @param maxInitialHeight 250 * the maximum initial height 251 * @see #setMinInitialHeight(int) 252 */ 253 public void setMaxInitialHeight(float maxInitialHeight) { 254 this.maxInitialHeight = maxInitialHeight; 255 } 256 257 /** 258 * Sets the minimum initial height of the terrain. 259 * 260 * @param minInitialHeight 261 * the minimum initial height 262 * @see #setMaxInitialHeight(int) 263 */ 264 public void setMinInitialHeight(float minInitialHeight) { 265 this.minInitialHeight = minInitialHeight; 266 } 267 268 /** 269 * Sets the distance between each node of the heightmap. 270 * 271 * @param nodeDistance 272 * the distance between each node 273 */ 274 public void setNodeDistance(float nodeDistance) { 275 this.nodeDistance = nodeDistance; 276 } 277 278 /** 279 * Sets the time-speed between each iteration of the fluid 280 * simulation algortithm. 281 * 282 * @param timeStep 283 * the time-step between each iteration 284 */ 285 public void setTimeStep(float timeStep) { 286 this.timeStep = timeStep; 287 } 288 289 /** 290 * Sets the viscosity of the simulated fuid. 291 * 292 * @param viscosity 293 * the viscosity of the fluid 294 */ 295 public void setViscosity(float viscosity) { 296 this.viscosity = viscosity; 297 } 298 299 /** 300 * Sets the speed at which the waves trave. 301 * 302 * @param waveSpeed 303 * the speed at which the waves travel 304 */ 305 public void setWaveSpeed(float waveSpeed) { 306 this.waveSpeed = waveSpeed; 307 } 308} 309