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 */ 32 package com.jme3.terrain.heightmap; 33 34 import java.util.Random; 35 import 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 */ 48 public 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 */ FluidSimHeightMap(int size, int iterations, float minInitialHeight, float maxInitialHeight, float viscosity, float waveSpeed, float timestep, float nodeDistance, long seed)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 */ FluidSimHeightMap(int size, int iterations)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 */ load()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 randomRange(Random random, float min, float max)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 */ setIterations(int iterations)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 */ setMaxInitialHeight(float maxInitialHeight)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 */ setMinInitialHeight(float minInitialHeight)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 */ setNodeDistance(float nodeDistance)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 */ setTimeStep(float timeStep)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 */ setViscosity(float viscosity)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 */ setWaveSpeed(float waveSpeed)305 public void setWaveSpeed(float waveSpeed) { 306 this.waveSpeed = waveSpeed; 307 } 308 } 309