• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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