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 */ 32 package com.jme3.terrain.heightmap; 33 34 import com.jme3.math.FastMath; 35 import java.util.Random; 36 import java.util.logging.Level; 37 import java.util.logging.Logger; 38 39 /** 40 * Creates an heightmap based on the fault algorithm. Each iteration, a random line 41 * crossing the map is generated. On one side height values are raised, on the other side 42 * lowered. 43 * @author cghislai 44 */ 45 public class FaultHeightMap extends AbstractHeightMap { 46 47 private static final Logger logger = Logger.getLogger(FaultHeightMap.class.getName()); 48 /** 49 * Values on one side are lowered, on the other side increased, 50 * creating a step at the fault line 51 */ 52 public static final int FAULTTYPE_STEP = 0; 53 /** 54 * Values on one side are lowered, then increase lineary while crossing 55 * the fault line to the other side. The fault line will be a inclined 56 * plane 57 */ 58 public static final int FAULTTYPE_LINEAR = 1; 59 /** 60 * Values are lowered on one side, increased on the other, creating a 61 * cosine curve on the fault line 62 */ 63 public static final int FAULTTYPE_COSINE = 2; 64 /** 65 * Value are lowered on both side, but increased on the fault line 66 * creating a smooth ridge on the fault line. 67 */ 68 public static final int FAULTTYPE_SINE = 3; 69 /** 70 * A linear fault is created 71 */ 72 public static final int FAULTSHAPE_LINE = 10; 73 /** 74 * A circular fault is created. 75 */ 76 public static final int FAULTSHAPE_CIRCLE = 11; 77 private long seed; // A seed to feed the random 78 private int iterations; // iterations to perform 79 private float minFaultHeight; // the height modification applied 80 private float maxFaultHeight; // the height modification applied 81 private float minRange; // The range for linear and trigo faults 82 private float maxRange; // The range for linear and trigo faults 83 private float minRadius; // radii for circular fault 84 private float maxRadius; 85 private int faultType; // The type of fault 86 private int faultShape; // The type of fault 87 88 /** 89 * Constructor creates the fault. For faulttype other than STEP, a range can 90 * be provided. For faultshape circle, min and max radii can be provided. 91 * Don't forget to reload the map if you have set parameters after the constructor 92 * call. 93 * @param size The size of the heightmap 94 * @param iterations Iterations to perform 95 * @param faultType Type of fault 96 * @param faultShape Shape of the fault -line or circle 97 * @param minFaultHeight Height modified on each side 98 * @param maxFaultHeight Height modified on each side 99 * @param seed A seed to feed the Random generator 100 * @see setFaultRange, setMinRadius, setMaxRadius 101 */ FaultHeightMap(int size, int iterations, int faultType, int faultShape, float minFaultHeight, float maxFaultHeight, long seed)102 public FaultHeightMap(int size, int iterations, int faultType, int faultShape, float minFaultHeight, float maxFaultHeight, long seed) throws Exception { 103 if (size < 0 || iterations < 0) { 104 throw new Exception("Size and iterations must be greater than 0!"); 105 } 106 this.size = size; 107 this.iterations = iterations; 108 this.faultType = faultType; 109 this.faultShape = faultShape; 110 this.minFaultHeight = minFaultHeight; 111 this.maxFaultHeight = maxFaultHeight; 112 this.seed = seed; 113 this.minRange = minFaultHeight; 114 this.maxRange = maxFaultHeight; 115 this.minRadius = size / 10; 116 this.maxRadius = size / 4; 117 load(); 118 } 119 120 /** 121 * Create an heightmap with linear step faults. 122 * @param size size of heightmap 123 * @param iterations number of iterations 124 * @param minFaultHeight Height modified on each side 125 * @param maxFaultHeight Height modified on each side 126 */ FaultHeightMap(int size, int iterations, float minFaultHeight, float maxFaultHeight)127 public FaultHeightMap(int size, int iterations, float minFaultHeight, float maxFaultHeight) throws Exception { 128 this(size, iterations, FAULTTYPE_STEP, FAULTSHAPE_LINE, minFaultHeight, maxFaultHeight, new Random().nextLong()); 129 } 130 131 @Override load()132 public boolean load() { 133 // clean up data if needed. 134 if (null != heightData) { 135 unloadHeightMap(); 136 } 137 heightData = new float[size * size]; 138 float[][] tempBuffer = new float[size][size]; 139 Random random = new Random(seed); 140 141 for (int i = 0; i < iterations; i++) { 142 addFault(tempBuffer, random); 143 } 144 145 for (int i = 0; i < size; i++) { 146 for (int j = 0; j < size; j++) { 147 setHeightAtPoint(tempBuffer[i][j], i, j); 148 } 149 } 150 151 normalizeTerrain(NORMALIZE_RANGE); 152 153 logger.log(Level.INFO, "Fault heightmap generated"); 154 return true; 155 } 156 addFault(float[][] tempBuffer, Random random)157 protected void addFault(float[][] tempBuffer, Random random) { 158 float faultHeight = minFaultHeight + random.nextFloat() * (maxFaultHeight - minFaultHeight); 159 float range = minRange + random.nextFloat() * (maxRange - minRange); 160 switch (faultShape) { 161 case FAULTSHAPE_LINE: 162 addLineFault(tempBuffer, random, faultHeight, range); 163 break; 164 case FAULTSHAPE_CIRCLE: 165 addCircleFault(tempBuffer, random, faultHeight, range); 166 break; 167 } 168 } 169 addLineFault(float[][] tempBuffer, Random random, float faultHeight, float range)170 protected void addLineFault(float[][] tempBuffer, Random random, float faultHeight, float range) { 171 int x1 = random.nextInt(size); 172 int x2 = random.nextInt(size); 173 int y1 = random.nextInt(size); 174 int y2 = random.nextInt(size); 175 176 177 for (int i = 0; i < size; i++) { 178 for (int j = 0; j < size; j++) { 179 float dist = ((x2 - x1) * (j - y1) - (y2 - y1) * (i - x1)) 180 / (FastMath.sqrt(FastMath.sqr(x2 - x1) + FastMath.sqr(y2 - y1))); 181 tempBuffer[i][j] += calcHeight(dist, random, faultHeight, range); 182 } 183 } 184 } 185 addCircleFault(float[][] tempBuffer, Random random, float faultHeight, float range)186 protected void addCircleFault(float[][] tempBuffer, Random random, float faultHeight, float range) { 187 float radius = random.nextFloat() * (maxRadius - minRadius) + minRadius; 188 int intRadius = (int) FastMath.floor(radius); 189 // Allox circle center to be out of map if not by more than radius. 190 // Unlucky cases will put them in the far corner, with the circle 191 // entirely outside heightmap 192 int x = random.nextInt(size + 2 * intRadius) - intRadius; 193 int y = random.nextInt(size + 2 * intRadius) - intRadius; 194 195 for (int i = 0; i < size; i++) { 196 for (int j = 0; j < size; j++) { 197 float dist; 198 if (i != x || j != y) { 199 int dx = i - x; 200 int dy = j - y; 201 float dmag = FastMath.sqrt(FastMath.sqr(dx) + FastMath.sqr(dy)); 202 float rx = x + dx / dmag * radius; 203 float ry = y + dy / dmag * radius; 204 dist = FastMath.sign(dmag - radius) 205 * FastMath.sqrt(FastMath.sqr(i - rx) + FastMath.sqr(j - ry)); 206 } else { 207 dist = 0; 208 } 209 tempBuffer[i][j] += calcHeight(dist, random, faultHeight, range); 210 } 211 } 212 } 213 calcHeight(float dist, Random random, float faultHeight, float range)214 protected float calcHeight(float dist, Random random, float faultHeight, float range) { 215 switch (faultType) { 216 case FAULTTYPE_STEP: { 217 return FastMath.sign(dist) * faultHeight; 218 } 219 case FAULTTYPE_LINEAR: { 220 if (FastMath.abs(dist) > range) { 221 return FastMath.sign(dist) * faultHeight; 222 } 223 float f = FastMath.abs(dist) / range; 224 return FastMath.sign(dist) * faultHeight * f; 225 } 226 case FAULTTYPE_SINE: { 227 if (FastMath.abs(dist) > range) { 228 return -faultHeight; 229 } 230 float f = dist / range; 231 // We want -1 at f=-1 and f=1; 1 at f=0 232 return FastMath.sin((1 + 2 * f) * FastMath.PI / 2) * faultHeight; 233 } 234 case FAULTTYPE_COSINE: { 235 if (FastMath.abs(dist) > range) { 236 return -FastMath.sign(dist) * faultHeight; 237 } 238 float f = dist / range; 239 float val = FastMath.cos((1 + f) * FastMath.PI / 2) * faultHeight; 240 return val; 241 } 242 } 243 //shoudn't go here 244 throw new RuntimeException("Code needs update to switch allcases"); 245 } 246 getFaultShape()247 public int getFaultShape() { 248 return faultShape; 249 } 250 setFaultShape(int faultShape)251 public void setFaultShape(int faultShape) { 252 this.faultShape = faultShape; 253 } 254 getFaultType()255 public int getFaultType() { 256 return faultType; 257 } 258 setFaultType(int faultType)259 public void setFaultType(int faultType) { 260 this.faultType = faultType; 261 } 262 getIterations()263 public int getIterations() { 264 return iterations; 265 } 266 setIterations(int iterations)267 public void setIterations(int iterations) { 268 this.iterations = iterations; 269 } 270 getMaxFaultHeight()271 public float getMaxFaultHeight() { 272 return maxFaultHeight; 273 } 274 setMaxFaultHeight(float maxFaultHeight)275 public void setMaxFaultHeight(float maxFaultHeight) { 276 this.maxFaultHeight = maxFaultHeight; 277 } 278 getMaxRadius()279 public float getMaxRadius() { 280 return maxRadius; 281 } 282 setMaxRadius(float maxRadius)283 public void setMaxRadius(float maxRadius) { 284 this.maxRadius = maxRadius; 285 } 286 getMaxRange()287 public float getMaxRange() { 288 return maxRange; 289 } 290 setMaxRange(float maxRange)291 public void setMaxRange(float maxRange) { 292 this.maxRange = maxRange; 293 } 294 getMinFaultHeight()295 public float getMinFaultHeight() { 296 return minFaultHeight; 297 } 298 setMinFaultHeight(float minFaultHeight)299 public void setMinFaultHeight(float minFaultHeight) { 300 this.minFaultHeight = minFaultHeight; 301 } 302 getMinRadius()303 public float getMinRadius() { 304 return minRadius; 305 } 306 setMinRadius(float minRadius)307 public void setMinRadius(float minRadius) { 308 this.minRadius = minRadius; 309 } 310 getMinRange()311 public float getMinRange() { 312 return minRange; 313 } 314 setMinRange(float minRange)315 public void setMinRange(float minRange) { 316 this.minRange = minRange; 317 } 318 getSeed()319 public long getSeed() { 320 return seed; 321 } 322 setSeed(long seed)323 public void setSeed(long seed) { 324 this.seed = seed; 325 } 326 } 327