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