001package squidpony.squidgrid.mapping; 002 003import squidpony.ArrayTools; 004import squidpony.LZSPlus; 005import squidpony.squidmath.Coord; 006import squidpony.squidmath.FastNoise; 007import squidpony.squidmath.GWTRNG; 008import squidpony.squidmath.GreasedRegion; 009import squidpony.squidmath.IntVLA; 010import squidpony.squidmath.Noise; 011import squidpony.squidmath.Noise.Noise2D; 012import squidpony.squidmath.Noise.Noise3D; 013import squidpony.squidmath.Noise.Noise4D; 014import squidpony.squidmath.NumberTools; 015 016import java.io.Serializable; 017import java.util.Arrays; 018 019/** 020 * Can be used to generate world maps with a wide variety of data, starting with height, temperature and moisture. 021 * From there, you can determine biome information in as much detail as your game needs, with default implementations 022 * available; one assigns a single biome to each cell based on heat/moisture, and the other gives a gradient between two 023 * biome types for every cell. The maps this produces with {@link SphereMap} are valid for spherical world projections, 024 * while the maps from {@link TilingMap} are for toroidal world projections and will wrap from edge to opposite edge 025 * seamlessly thanks to <a href="https://www.gamedev.net/blog/33/entry-2138456-seamless-noise/">a technique from the 026 * Accidental Noise Library</a> that involves getting a 2D slice of 4D Simplex noise. Because of how Simplex noise 027 * works, this also allows extremely high zoom levels for all types of map as long as certain parameters are within 028 * reason. Other world maps produce more conventional shapes, like {@link SpaceViewMap} and {@link RotatingSpaceMap} 029 * make a view of a marble-like world from space, and others make more unconventional shapes, like {@link EllipticalMap} 030 * or {@link EllipticalHammerMap}, which form a 2:1 ellipse shape that accurately keeps sizes but not relative shapes, 031 * {@link RoundSideMap}, which forms a pill-shape, and {@link HyperellipticalMap}, which takes parameters so it can fit 032 * any shape between a circle or ellipse and a rectangle (the default is a slightly squared-off ellipse). You can access 033 * the height map with the {@link #heightData} field, the heat map with the {@link #heatData} field, the moisture map 034 * with the {@link #moistureData} field, and a special map that stores ints representing the codes for various ranges of 035 * elevation (0 to 8 inclusive, with 0 the deepest ocean and 8 the highest mountains) with {@link #heightCodeData}. The 036 * last map should be noted as being the simplest way to find what is land and what is water; any height code 4 or 037 * greater is land, and any height code 3 or less is water. 038 * <br> 039 * Biome mapping is likely to need customization per-game, but some good starting points are {@link SimpleBiomeMapper}, 040 * which stores one biome per cell, and {@link DetailedBiomeMapper}, which gives each cell a midway value between two 041 * biomes. 042 */ 043public abstract class WorldMapGenerator implements Serializable { 044 private static final long serialVersionUID = 1L; 045 public final int width, height; 046 public int seedA, seedB, cacheA, cacheB; 047 public GWTRNG rng; 048 public final double[][] heightData, heatData, moistureData; 049 public final GreasedRegion landData; 050 public final int[][] heightCodeData; 051 public double landModifier = -1.0, heatModifier = 1.0, 052 minHeight = Double.POSITIVE_INFINITY, maxHeight = Double.NEGATIVE_INFINITY, 053 minHeightActual = Double.POSITIVE_INFINITY, maxHeightActual = Double.NEGATIVE_INFINITY, 054 minHeat = Double.POSITIVE_INFINITY, maxHeat = Double.NEGATIVE_INFINITY, 055 minWet = Double.POSITIVE_INFINITY, maxWet = Double.NEGATIVE_INFINITY; 056 protected double centerLongitude; 057 058 public int zoom, startX, startY, usedWidth, usedHeight; 059 protected IntVLA startCacheX = new IntVLA(8), startCacheY = new IntVLA(8); 060 protected int zoomStartX, zoomStartY; 061 062 /** 063 * A FastNoise that has a higher frequency than that class defaults to, which is useful for maps here. With the 064 * default FastNoise frequency of 1f/32f, the maps this produces are giant blurs. 065 * <br> 066 * Even though this is a FastNoise and so technically can be edited, that seems to have issues when there's more 067 * than one WorldMapGenerator that uses this field. So you can feel free to use this as a Noise2D or Noise3D when 068 * generators need one, but don't change it too much, if at all. 069 */ 070 public static final FastNoise DEFAULT_NOISE = new FastNoise(0x1337CAFE, 1f, FastNoise.SIMPLEX, 1); 071 072 /** 073 * Used to implement most of the copy constructor for subclasses; this cannot copy Noise implementations and leaves 074 * that up to the subclass, but will copy all non-static fields defined in WorldMapGenerator from other. 075 * @param other a WorldMapGenerator (subclass) to copy fields from 076 */ 077 protected WorldMapGenerator(WorldMapGenerator other) { 078 width = other.width; 079 height = other.height; 080 usedWidth = other.usedWidth; 081 usedHeight = other.usedHeight; 082 landModifier = other.landModifier; 083 heatModifier = other.heatModifier; 084 minHeat = other.minHeat; 085 maxHeat = other.maxHeat; 086 minHeight = other.minHeight; 087 maxHeight = other.maxHeight; 088 minHeightActual = other.minHeightActual; 089 maxHeightActual = other.maxHeightActual; 090 minWet = other.minWet; 091 maxWet = other.maxWet; 092 centerLongitude = other.centerLongitude; 093 zoom = other.zoom; 094 startX = other.startX; 095 startY = other.startY; 096 startCacheX.addAll(other.startCacheX); 097 startCacheY.addAll(other.startCacheY); 098 zoomStartX = other.zoomStartX; 099 zoomStartY = other.zoomStartY; 100 seedA = other.seedA; 101 seedB = other.seedB; 102 cacheA = other.cacheA; 103 cacheB = other.cacheB; 104 rng = other.rng.copy(); 105 heightData = ArrayTools.copy(other.heightData); 106 heatData = ArrayTools.copy(other.heatData); 107 moistureData = ArrayTools.copy(other.moistureData); 108 landData = other.landData.copy(); 109 heightCodeData = ArrayTools.copy(other.heightCodeData); 110 } 111 112 /** 113 * Gets the longitude line the map is centered on, which should usually be between 0 and 2 * PI. 114 * @return the longitude line the map is centered on, in radians from 0 to 2 * PI 115 */ 116 public double getCenterLongitude() { 117 return centerLongitude; 118 } 119 120 /** 121 * Sets the center longitude line to a longitude measured in radians, from 0 to 2 * PI. Positive arguments will be 122 * corrected with modulo, but negative ones may not always act as expected, and are strongly discouraged. 123 * @param centerLongitude the longitude to center the map projection on, from 0 to 2 * PI (can be any non-negative double). 124 */ 125 public void setCenterLongitude(double centerLongitude) { 126 this.centerLongitude = centerLongitude % 6.283185307179586; 127 } 128 129 public static final double 130 deepWaterLower = -1.0, deepWaterUpper = -0.7, // 0 131 mediumWaterLower = -0.7, mediumWaterUpper = -0.3, // 1 132 shallowWaterLower = -0.3, shallowWaterUpper = -0.1, // 2 133 coastalWaterLower = -0.1, coastalWaterUpper = 0.02, // 3 134 sandLower = 0.02, sandUpper = 0.12, // 4 135 grassLower = 0.14, grassUpper = 0.35, // 5 136 forestLower = 0.35, forestUpper = 0.6, // 6 137 rockLower = 0.6, rockUpper = 0.8, // 7 138 snowLower = 0.8, snowUpper = 1.0; // 8 139 140 protected static double removeExcess(double radians) 141 { 142 radians *= 0.6366197723675814; 143 final int floor = (radians >= 0.0 ? (int) radians : (int) radians - 1); 144 return (radians - (floor & -2) - ((floor & 1) << 1)) * (Math.PI); 145// if(radians < -Math.PI || radians > Math.PI) 146// System.out.println("UH OH, radians produced: " + radians); 147// if(Math.random() < 0.00001) 148// System.out.println(radians); 149// return radians; 150 151 } 152 /** 153 * Constructs a WorldMapGenerator (this class is abstract, so you should typically call this from a subclass or as 154 * part of an anonymous class that implements {@link #regenerate(int, int, int, int, double, double, int, int)}). 155 * Always makes a 256x256 map. If you were using {@link WorldMapGenerator#WorldMapGenerator(long, int, int)}, then 156 * this would be the same as passing the parameters {@code 0x1337BABE1337D00DL, 256, 256}. 157 */ 158 protected WorldMapGenerator() 159 { 160 this(0x1337BABE1337D00DL, 256, 256); 161 } 162 /** 163 * Constructs a WorldMapGenerator (this class is abstract, so you should typically call this from a subclass or as 164 * part of an anonymous class that implements {@link #regenerate(int, int, int, int, double, double, int, int)}). 165 * Takes only the width/height of the map. The initial seed is set to the same large long 166 * every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and 167 * height of the map cannot be changed after the fact, but you can zoom in. 168 * 169 * @param mapWidth the width of the map(s) to generate; cannot be changed later 170 * @param mapHeight the height of the map(s) to generate; cannot be changed later 171 */ 172 protected WorldMapGenerator(int mapWidth, int mapHeight) 173 { 174 this(0x1337BABE1337D00DL, mapWidth, mapHeight); 175 } 176 /** 177 * Constructs a WorldMapGenerator (this class is abstract, so you should typically call this from a subclass or as 178 * part of an anonymous class that implements {@link #regenerate(int, int, int, int, double, double, int, int)}). 179 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 180 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 181 * The width and height of the map cannot be changed after the fact, but you can zoom in. 182 * 183 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 184 * @param mapWidth the width of the map(s) to generate; cannot be changed later 185 * @param mapHeight the height of the map(s) to generate; cannot be changed later 186 */ 187 protected WorldMapGenerator(long initialSeed, int mapWidth, int mapHeight) 188 { 189 width = mapWidth; 190 height = mapHeight; 191 usedWidth = width; 192 usedHeight = height; 193 seedA = (int) (initialSeed & 0xFFFFFFFFL); 194 seedB = (int) (initialSeed >>> 32); 195 cacheA = ~seedA; 196 cacheB = ~seedB; 197 rng = new GWTRNG(seedA, seedB); 198 heightData = new double[width][height]; 199 heatData = new double[width][height]; 200 moistureData = new double[width][height]; 201 landData = new GreasedRegion(width, height); 202 heightCodeData = new int[width][height]; 203 204// riverData = new GreasedRegion(width, height); 205// lakeData = new GreasedRegion(width, height); 206// partialRiverData = new GreasedRegion(width, height); 207// partialLakeData = new GreasedRegion(width, height); 208// workingData = new GreasedRegion(width, height); 209 } 210 211 /** 212 * Generates a world using a random RNG state and all parameters randomized. 213 * The worlds this produces will always have width and height as specified in the constructor (default 256x256). 214 * You can call {@link #zoomIn(int, int, int)} to double the resolution and center on the specified area, but the width 215 * and height of the 2D arrays this changed, such as {@link #heightData} and {@link #moistureData} will be the same. 216 */ 217 public void generate() 218 { 219 generate(rng.nextLong()); 220 } 221 222 /** 223 * Generates a world using the specified RNG state as a long. Other parameters will be randomized, using the same 224 * RNG state to start with. 225 * The worlds this produces will always have width and height as specified in the constructor (default 256x256). 226 * You can call {@link #zoomIn(int, int, int)} to double the resolution and center on the specified area, but the width 227 * and height of the 2D arrays this changed, such as {@link #heightData} and {@link #moistureData} will be the same. 228 * @param state the state to give this generator's RNG; if the same as the last call, this will reuse data 229 */ 230 public void generate(long state) { 231 generate(-1.0, -1.0, state); 232 } 233 234 /** 235 * Generates a world using the specified RNG state as a long, with specific land and heat modifiers that affect 236 * the land-water ratio and the average temperature, respectively. 237 * The worlds this produces will always have width and height as specified in the constructor (default 256x256). 238 * You can call {@link #zoomIn(int, int, int)} to double the resolution and center on the specified area, but the width 239 * and height of the 2D arrays this changed, such as {@link #heightData} and {@link #moistureData} will be the same. 240 * @param landMod 1.0 is Earth-like, less than 1 is more-water, more than 1 is more-land; a random value will be used if this is negative 241 * @param heatMod 1.125 is Earth-like, less than 1 is cooler, more than 1 is hotter; a random value will be used if this is negative 242 * @param state the state to give this generator's RNG; if the same as the last call, this will reuse data 243 */ 244 public void generate(double landMod, double heatMod, long state) 245 { 246 if(cacheA != (int) (state & 0xFFFFFFFFL) || cacheB != (int) (state >>> 32) || 247 landMod != landModifier || heatMod != heatModifier) 248 { 249 seedA = (int) (state & 0xFFFFFFFFL); 250 seedB = (int) (state >>> 32); 251 zoom = 0; 252 startCacheX.clear(); 253 startCacheY.clear(); 254 startCacheX.add(0); 255 startCacheY.add(0); 256 zoomStartX = width >> 1; 257 zoomStartY = height >> 1; 258 259 } 260 //System.out.printf("generate, zoomStartX: %d, zoomStartY: %d\n", zoomStartX, zoomStartY); 261 262 regenerate(startX = (zoomStartX >> zoom) - (width >> 1 + zoom), startY = (zoomStartY >> zoom) - (height >> 1 + zoom), 263 //startCacheX.peek(), startCacheY.peek(), 264 usedWidth = (width >> zoom), usedHeight = (height >> zoom), landMod, heatMod, seedA, seedB); 265 } 266 267 /** 268 * Halves the resolution of the map and doubles the area it covers; the 2D arrays this uses keep their sizes. This 269 * version of zoomOut always zooms out from the center of the currently used area. 270 * <br> 271 * Only has an effect if you have previously zoomed in using {@link #zoomIn(int, int, int)} or its overload. 272 */ 273 public void zoomOut() 274 { 275 zoomOut(1, width >> 1, height >> 1); 276 } 277 /** 278 * Halves the resolution of the map and doubles the area it covers repeatedly, halving {@code zoomAmount} times; the 279 * 2D arrays this uses keep their sizes. This version of zoomOut allows you to specify where the zoom should be 280 * centered, using the current coordinates (if the map size is 256x256, then coordinates should be between 0 and 281 * 255, and will refer to the currently used area and not necessarily the full world size). 282 * <br> 283 * Only has an effect if you have previously zoomed in using {@link #zoomIn(int, int, int)} or its overload. 284 * @param zoomCenterX the center X position to zoom out from; if too close to an edge, this will stop moving before it would extend past an edge 285 * @param zoomCenterY the center Y position to zoom out from; if too close to an edge, this will stop moving before it would extend past an edge 286 */ 287 public void zoomOut(int zoomAmount, int zoomCenterX, int zoomCenterY) 288 { 289 zoomAmount = Math.min(zoom, zoomAmount); 290 if(zoomAmount == 0) return; 291 if(zoomAmount < 0) { 292 zoomIn(-zoomAmount, zoomCenterX, zoomCenterY); 293 return; 294 } 295 if(zoom > 0) 296 { 297 if(cacheA != seedA || cacheB != seedB) 298 { 299 generate(rng.nextLong()); 300 } 301 zoomStartX = Math.min(Math.max( 302 (zoomStartX + (zoomCenterX - (width >> 1))) >> zoomAmount, 303 width >> 1), (width << zoom - zoomAmount) - (width >> 1)); 304 zoomStartY = Math.min(Math.max( 305 (zoomStartY + (zoomCenterY - (height >> 1))) >> zoomAmount, 306 height >> 1), (height << zoom - zoomAmount) - (height >> 1)); 307// System.out.printf("zoomOut, zoomStartX: %d, zoomStartY: %d\n", zoomStartX, zoomStartY); 308 zoom -= zoomAmount; 309 startCacheX.pop(); 310 startCacheY.pop(); 311 startCacheX.add(Math.min(Math.max(startCacheX.pop() + (zoomCenterX >> zoom + 1) - (width >> zoom + 2), 312 0), width - (width >> zoom))); 313 startCacheY.add(Math.min(Math.max(startCacheY.pop() + (zoomCenterY >> zoom + 1) - (height >> zoom + 2), 314 0), height - (height >> zoom))); 315// zoomStartX = Math.min(Math.max((zoomStartX >> 1) + (zoomCenterX >> zoom + 1) - (width >> zoom + 2), 316// 0), width - (width >> zoom)); 317// zoomStartY = Math.min(Math.max((zoomStartY >> 1) + (zoomCenterY >> zoom + 1) - (height >> zoom + 2), 318// 0), height - (height >> zoom)); 319 regenerate(startX = (zoomStartX >> zoom) - (width >> zoom + 1), startY = (zoomStartY >> zoom) - (height >> zoom + 1), 320 //startCacheX.peek(), startCacheY.peek(), 321 usedWidth = width >> zoom, usedHeight = height >> zoom, 322 landModifier, heatModifier, cacheA, cacheB); 323 rng.setState(cacheA, cacheB); 324 } 325 326 } 327 /** 328 * Doubles the resolution of the map and halves the area it covers; the 2D arrays this uses keep their sizes. This 329 * version of zoomIn always zooms in to the center of the currently used area. 330 * <br> 331 * Although there is no technical restriction on maximum zoom, zooming in more than 5 times (64x scale or greater) 332 * will make the map appear somewhat less realistic due to rounded shapes appearing more bubble-like and less like a 333 * normal landscape. 334 */ 335 public void zoomIn() 336 { 337 zoomIn(1, width >> 1, height >> 1); 338 } 339 /** 340 * Doubles the resolution of the map and halves the area it covers repeatedly, doubling {@code zoomAmount} times; 341 * the 2D arrays this uses keep their sizes. This version of zoomIn allows you to specify where the zoom should be 342 * centered, using the current coordinates (if the map size is 256x256, then coordinates should be between 0 and 343 * 255, and will refer to the currently used area and not necessarily the full world size). 344 * <br> 345 * Although there is no technical restriction on maximum zoom, zooming in more than 5 times (64x scale or greater) 346 * will make the map appear somewhat less realistic due to rounded shapes appearing more bubble-like and less like a 347 * normal landscape. 348 * @param zoomCenterX the center X position to zoom in to; if too close to an edge, this will stop moving before it would extend past an edge 349 * @param zoomCenterY the center Y position to zoom in to; if too close to an edge, this will stop moving before it would extend past an edge 350 */ 351 public void zoomIn(int zoomAmount, int zoomCenterX, int zoomCenterY) 352 { 353 if(zoomAmount == 0) return; 354 if(zoomAmount < 0) 355 { 356 zoomOut(-zoomAmount, zoomCenterX, zoomCenterY); 357 return; 358 } 359 if(seedA != cacheA || seedB != cacheB) 360 { 361 generate(rng.nextLong()); 362 } 363 zoomStartX = Math.min(Math.max( 364 (zoomStartX + zoomCenterX - (width >> 1) << zoomAmount), 365 width >> 1), (width << zoom + zoomAmount) - (width >> 1)); 366// int oldZoomY = zoomStartY; 367 zoomStartY = Math.min(Math.max( 368 (zoomStartY + zoomCenterY - (height >> 1) << zoomAmount), 369 height >> 1), (height << zoom + zoomAmount) - (height >> 1)); 370// System.out.printf("zoomIn, zoomStartX: %d, zoomStartY: %d, oldZoomY: %d, unedited: %d, upperCap: %d\n", zoomStartX, zoomStartY, 371// oldZoomY, (oldZoomY + zoomCenterY - (height >> 1) << zoomAmount), (height << zoom + zoomAmount) - (height >> 1)); 372 zoom += zoomAmount; 373 if(startCacheX.isEmpty()) 374 { 375 startCacheX.add(0); 376 startCacheY.add(0); 377 } 378 else { 379 startCacheX.add(Math.min(Math.max(startCacheX.peek() + (zoomCenterX >> zoom - 1) - (width >> zoom + 1), 380 0), width - (width >> zoom))); 381 startCacheY.add(Math.min(Math.max(startCacheY.peek() + (zoomCenterY >> zoom - 1) - (height >> zoom + 1), 382 0), height - (height >> zoom))); 383 } 384 regenerate(startX = (zoomStartX >> zoom) - (width >> 1 + zoom), startY = (zoomStartY >> zoom) - (height >> 1 + zoom), 385 //startCacheX.peek(), startCacheY.peek(), 386 usedWidth = width >> zoom, usedHeight = height >> zoom, 387 landModifier, heatModifier, cacheA, cacheB); 388 rng.setState(cacheA, cacheB); 389 } 390 391 protected abstract void regenerate(int startX, int startY, int usedWidth, int usedHeight, 392 double landMod, double heatMod, int stateA, int stateB); 393 /** 394 * Given a latitude and longitude in radians (the conventional way of describing points on a globe), this gets the 395 * (x,y) Coord on the map projection this generator uses that corresponds to the given lat-lon coordinates. If this 396 * generator does not represent a globe (if it is toroidal, for instance) or if there is no "good way" to calculate 397 * the projection for a given lat-lon coordinate, this returns null. The default implementation always returns null. 398 * If this is a supported operation and the parameters are valid, this returns a Coord with x between 0 and 399 * {@link #width}, and y between 0 and {@link #height}, both exclusive. Automatically wraps the Coord's values using 400 * {@link #wrapX(int, int)} and {@link #wrapY(int, int)}. 401 * @param latitude the latitude, from {@code Math.PI * -0.5} to {@code Math.PI * 0.5} 402 * @param longitude the longitude, from {@code 0.0} to {@code Math.PI * 2.0} 403 * @return the point at the given latitude and longitude, as a Coord with x between 0 and {@link #width} and y between 0 and {@link #height}, or null if unsupported 404 */ 405 public Coord project(double latitude, double longitude) 406 { 407 return null; 408 } 409 410 public int codeHeight(final double high) 411 { 412 if(high < deepWaterUpper) 413 return 0; 414 if(high < mediumWaterUpper) 415 return 1; 416 if(high < shallowWaterUpper) 417 return 2; 418 if(high < coastalWaterUpper) 419 return 3; 420 if(high < sandUpper) 421 return 4; 422 if(high < grassUpper) 423 return 5; 424 if(high < forestUpper) 425 return 6; 426 if(high < rockUpper) 427 return 7; 428 return 8; 429 } 430 protected final int decodeX(final int coded) 431 { 432 return coded % width; 433 } 434 protected final int decodeY(final int coded) 435 { 436 return coded / width; 437 } 438 public int wrapX(final int x, final int y) { 439 return (x + width) % width; 440 } 441 public int wrapY(final int x, final int y) { 442 return (y + height) % height; 443 } 444 445// private static final Direction[] reuse = new Direction[6]; 446// private void appendDirToShuffle(RNG rng) { 447// rng.randomPortion(Direction.CARDINALS, reuse); 448// reuse[rng.next(2)] = Direction.DIAGONALS[rng.next(2)]; 449// reuse[4] = Direction.DIAGONALS[rng.next(2)]; 450// reuse[5] = Direction.OUTWARDS[rng.next(3)]; 451// } 452 453// protected void addRivers() 454// { 455// landData.refill(heightCodeData, 4, 999); 456// long rebuildState = rng.nextLong(); 457// //workingData.allOn(); 458// //.empty().insertRectangle(8, 8, width - 16, height - 16); 459// riverData.empty().refill(heightCodeData, 6, 100); 460// riverData.quasiRandomRegion(0.0036); 461// int[] starts = riverData.asTightEncoded(); 462// int len = starts.length, currentPos, choice, adjX, adjY, currX, currY, tcx, tcy, stx, sty, sbx, sby; 463// riverData.clear(); 464// lakeData.clear(); 465// PER_RIVER: 466// for (int i = 0; i < len; i++) { 467// workingData.clear(); 468// currentPos = starts[i]; 469// stx = tcx = currX = decodeX(currentPos); 470// sty = tcy = currY = decodeY(currentPos); 471// while (true) { 472// 473// double best = 999999; 474// choice = -1; 475// appendDirToShuffle(rng); 476// 477// for (int d = 0; d < 5; d++) { 478// adjX = wrapX(currX + reuse[d].deltaX); 479// /* 480// if (adjX < 0 || adjX >= width) 481// { 482// if(rng.next(4) == 0) 483// riverData.or(workingData); 484// continue PER_RIVER; 485// }*/ 486// adjY = wrapY(currY + reuse[d].deltaY); 487// if (heightData[adjX][adjY] < best && !workingData.contains(adjX, adjY)) { 488// best = heightData[adjX][adjY]; 489// choice = d; 490// tcx = adjX; 491// tcy = adjY; 492// } 493// } 494// currX = tcx; 495// currY = tcy; 496// if (best >= heightData[stx][sty]) { 497// tcx = rng.next(2); 498// adjX = wrapX(currX + ((tcx & 1) << 1) - 1); 499// adjY = wrapY(currY + (tcx & 2) - 1); 500// lakeData.insert(currX, currY); 501// lakeData.insert(wrapX(currX+1), currY); 502// lakeData.insert(wrapX(currX-1), currY); 503// lakeData.insert(currX, wrapY(currY+1)); 504// lakeData.insert(currX, wrapY(currY-1)); 505// 506// if(heightCodeData[adjX][adjY] <= 3) { 507// riverData.or(workingData); 508// continue PER_RIVER; 509// } 510// else if((heightData[adjX][adjY] -= 0.0002) < 0.0) { 511// if (rng.next(3) == 0) 512// riverData.or(workingData); 513// continue PER_RIVER; 514// } 515// tcx = rng.next(2); 516// adjX = wrapX(currX + ((tcx & 1) << 1) - 1); 517// adjY = wrapY(currY + (tcx & 2) - 1); 518// if(heightCodeData[adjX][adjY] <= 3) { 519// riverData.or(workingData); 520// continue PER_RIVER; 521// } 522// else if((heightData[adjX][adjY] -= 0.0002) < 0.0) { 523// if (rng.next(3) == 0) 524// riverData.or(workingData); 525// continue PER_RIVER; 526// } 527// } 528// if(choice != -1 && reuse[choice].isDiagonal()) 529// { 530// tcx = wrapX(currX - reuse[choice].deltaX); 531// tcy = wrapY(currY - reuse[choice].deltaY); 532// if(heightData[tcx][currY] <= heightData[currX][tcy] && !workingData.contains(tcx, currY)) 533// { 534// if(heightCodeData[tcx][currY] < 3 || riverData.contains(tcx, currY)) 535// { 536// riverData.or(workingData); 537// continue PER_RIVER; 538// } 539// workingData.insert(tcx, currY); 540// } 541// else if(!workingData.contains(currX, tcy)) 542// { 543// if(heightCodeData[currX][tcy] < 3 || riverData.contains(currX, tcy)) 544// { 545// riverData.or(workingData); 546// continue PER_RIVER; 547// } 548// workingData.insert(currX, tcy); 549// 550// } 551// } 552// if(heightCodeData[currX][currY] < 3 || riverData.contains(currX, currY)) 553// { 554// riverData.or(workingData); 555// continue PER_RIVER; 556// } 557// workingData.insert(currX, currY); 558// } 559// } 560// 561// GreasedRegion tempData = new GreasedRegion(width, height); 562// int riverCount = riverData.size() >> 4, currentMax = riverCount >> 3, idx = 0, prevChoice; 563// for (int h = 5; h < 9; h++) { //, currentMax += riverCount / 18 564// workingData.empty().refill(heightCodeData, h).and(riverData); 565// RIVER: 566// for (int j = 0; j < currentMax && idx < riverCount; j++) { 567// double vdc = VanDerCorputQRNG.weakDetermine(idx++), best = -999999; 568// currentPos = workingData.atFractionTight(vdc); 569// if(currentPos < 0) 570// break; 571// stx = sbx = tcx = currX = decodeX(currentPos); 572// sty = sby = tcy = currY = decodeY(currentPos); 573// appendDirToShuffle(rng); 574// choice = -1; 575// prevChoice = -1; 576// for (int d = 0; d < 5; d++) { 577// adjX = wrapX(currX + reuse[d].deltaX); 578// adjY = wrapY(currY + reuse[d].deltaY); 579// if (heightData[adjX][adjY] > best) { 580// best = heightData[adjX][adjY]; 581// prevChoice = choice; 582// choice = d; 583// sbx = tcx; 584// sby = tcy; 585// tcx = adjX; 586// tcy = adjY; 587// } 588// } 589// currX = sbx; 590// currY = sby; 591// if (prevChoice != -1 && heightCodeData[currX][currY] >= 4) { 592// if (reuse[prevChoice].isDiagonal()) { 593// tcx = wrapX(currX - reuse[prevChoice].deltaX); 594// tcy = wrapY(currY - reuse[prevChoice].deltaY); 595// if (heightData[tcx][currY] <= heightData[currX][tcy]) { 596// if(heightCodeData[tcx][currY] < 3) 597// { 598// riverData.or(tempData); 599// continue; 600// } 601// tempData.insert(tcx, currY); 602// } 603// else 604// { 605// if(heightCodeData[currX][tcy] < 3) 606// { 607// riverData.or(tempData); 608// continue; 609// } 610// tempData.insert(currX, tcy); 611// } 612// } 613// if(heightCodeData[currX][currY] < 3) 614// { 615// riverData.or(tempData); 616// continue; 617// } 618// tempData.insert(currX, currY); 619// } 620// 621// while (true) { 622// best = -999999; 623// appendDirToShuffle(rng); 624// choice = -1; 625// for (int d = 0; d < 6; d++) { 626// adjX = wrapX(currX + reuse[d].deltaX); 627// adjY = wrapY(currY + reuse[d].deltaY); 628// if (heightData[adjX][adjY] > best && !riverData.contains(adjX, adjY)) { 629// best = heightData[adjX][adjY]; 630// choice = d; 631// sbx = adjX; 632// sby = adjY; 633// } 634// } 635// currX = sbx; 636// currY = sby; 637// if (choice != -1) { 638// if (reuse[choice].isDiagonal()) { 639// tcx = wrapX(currX - reuse[choice].deltaX); 640// tcy = wrapY(currY - reuse[choice].deltaY); 641// if (heightData[tcx][currY] <= heightData[currX][tcy]) { 642// if(heightCodeData[tcx][currY] < 3) 643// { 644// riverData.or(tempData); 645// continue RIVER; 646// } 647// tempData.insert(tcx, currY); 648// } 649// else 650// { 651// if(heightCodeData[currX][tcy] < 3) 652// { 653// riverData.or(tempData); 654// continue RIVER; 655// } 656// tempData.insert(currX, tcy); 657// } 658// } 659// if(heightCodeData[currX][currY] < 3) 660// { 661// riverData.or(tempData); 662// continue RIVER; 663// } 664// tempData.insert(currX, currY); 665// } 666// else 667// { 668// riverData.or(tempData); 669// tempData.clear(); 670// continue RIVER; 671// } 672// if (best <= heightData[stx][sty] || heightData[currX][currY] > rng.nextDouble(280.0)) { 673// riverData.or(tempData); 674// tempData.clear(); 675// if(heightCodeData[currX][currY] < 3) 676// continue RIVER; 677// lakeData.insert(currX, currY); 678// sbx = rng.next(8); 679// sbx &= sbx >>> 4; 680// if ((sbx & 1) == 0) 681// lakeData.insert(wrapX(currX + 1), currY); 682// if ((sbx & 2) == 0) 683// lakeData.insert(wrapX(currX - 1), currY); 684// if ((sbx & 4) == 0) 685// lakeData.insert(currX, wrapY(currY + 1)); 686// if ((sbx & 8) == 0) 687// lakeData.insert(currX, wrapY(currY - 1)); 688// sbx = rng.next(2); 689// lakeData.insert(wrapX(currX + (-(sbx & 1) | 1)), wrapY(currY + ((sbx & 2) - 1))); // random diagonal 690// lakeData.insert(currX, wrapY(currY + ((sbx & 2) - 1))); // ortho next to random diagonal 691// lakeData.insert(wrapX(currX + (-(sbx & 1) | 1)), currY); // ortho next to random diagonal 692// 693// continue RIVER; 694// } 695// } 696// } 697// 698// } 699// 700// rng.setState(rebuildState); 701// } 702 703 public interface BiomeMapper 704 { 705 /** 706 * Gets the most relevant biome code for a given x,y point on the map. Some mappers can store more than one 707 * biome at a location, but only the one with the highest influence will be returned by this method. Biome codes 708 * are always ints, and are typically between 0 and 60, both inclusive; they are meant to be used as indices 709 * into a table of names or other objects that identify a biome, accessible via {@link #getBiomeNameTable()}. 710 * Although different classes may define biome codes differently, they should all be able to be used as indices 711 * into the String array returned by getBiomeNameTable(). 712 * @param x the x-coordinate on the map 713 * @param y the y-coordinate on the map 714 * @return an int that can be used as an index into the array returned by {@link #getBiomeNameTable()} 715 */ 716 int getBiomeCode(int x, int y); 717 718 /** 719 * Gets a heat code for a given x,y point on a map, usually as an int between 0 and 5 inclusive. Some 720 * implementations may use more or less detail for heat codes, but 0 is always the coldest code used, and the 721 * highest value this can return for a given implementation refers to the hottest code used. 722 * @param x the x-coordinate on the map 723 * @param y the y-coordinate on the map 724 * @return an int that can be used to categorize how hot an area is, with 0 as coldest 725 */ 726 int getHeatCode(int x, int y); 727 /** 728 * Gets a moisture code for a given x,y point on a map, usually as an int between 0 and 5 inclusive. Some 729 * implementations may use more or less detail for moisture codes, but 0 is always the driest code used, and the 730 * highest value this can return for a given implementation refers to the wettest code used. Some 731 * implementations may allow seasonal change in moisture, e.g. monsoon seasons, to be modeled differently from 732 * average precipitation in an area, but the default assumption is that this describes the average amount of 733 * moisture (rain, humidity, and possibly snow/hail or other precipitation) that an area receives annually. 734 * @param x the x-coordinate on the map 735 * @param y the y-coordinate on the map 736 * @return an int that can be used to categorize how much moisture an area tends to receive, with 0 as driest 737 */ 738 int getMoistureCode(int x, int y); 739 740 /** 741 * Gets a String array where biome codes can be used as indices to look up a name for the biome they refer to. A 742 * sample table is in {@link SimpleBiomeMapper#biomeTable}; the 61-element array format documented for that 743 * field is encouraged for implementing classes if they use 6 levels of heat and 6 levels of moisture, and track 744 * rivers, coastlines, lakes, and oceans as potentially different types of terrain. Biome codes can be obtained 745 * with {@link #getBiomeCode(int, int)}, or for some implementing classes other methods may provide more 746 * detailed information. 747 * @return a String array that often contains 61 elements, to be used with biome codes as indices. 748 */ 749 String[] getBiomeNameTable(); 750 /** 751 * Analyzes the last world produced by the given WorldMapGenerator and uses all of its generated information to 752 * assign biome codes for each cell (along with heat and moisture codes). After calling this, biome codes can be 753 * retrieved with {@link #getBiomeCode(int, int)} and used as indices into {@link #getBiomeNameTable()} or a 754 * custom biome table. 755 * @param world a WorldMapGenerator that should have generated at least one map; it may be at any zoom 756 */ 757 void makeBiomes(WorldMapGenerator world); 758 } 759 /** 760 * A way to get biome information for the cells on a map when you only need a single value to describe a biome, such 761 * as "Grassland" or "TropicalRainforest". 762 * <br> 763 * To use: 1, Construct a SimpleBiomeMapper (constructor takes no arguments). 2, call 764 * {@link #makeBiomes(WorldMapGenerator)} with a WorldMapGenerator that has already produced at least one world map. 765 * 3, get biome codes from the {@link #biomeCodeData} field, where a code is an int that can be used as an index 766 * into the {@link #biomeTable} static field to get a String name for a biome type, or used with an alternate biome 767 * table of your design. Biome tables in this case are 61-element arrays organized into groups of 6 elements, with 768 * the last element reserved for empty space where the map doesn't cover (as with some map projections). Each 769 * group goes from the coldest temperature first to the warmest temperature last in the group. The first group of 6 770 * contains the dryest biomes, the next 6 are medium-dry, the next are slightly-dry, the next slightly-wet, then 771 * medium-wet, then wettest. After this first block of dry-to-wet groups, there is a group of 6 for coastlines, a 772 * group of 6 for rivers, a group of 6 for lakes, a group of 6 for oceans, and then one element for space outside 773 * the map. The last element, with code 60, is by convention the String "Empty", but normally the code should be 774 * enough to tell that a space is off-map. This also assigns moisture codes and heat codes from 0 to 5 for each 775 * cell, which may be useful to simplify logic that deals with those factors. 776 */ 777 public static class SimpleBiomeMapper implements BiomeMapper 778 { 779 /** 780 * The heat codes for the analyzed map, from 0 to 5 inclusive, with 0 coldest and 5 hottest. 781 */ 782 public int[][] heatCodeData, 783 /** 784 * The moisture codes for the analyzed map, from 0 to 5 inclusive, with 0 driest and 5 wettest. 785 */ 786 moistureCodeData, 787 /** 788 * The biome codes for the analyzed map, from 0 to 60 inclusive. You can use {@link #biomeTable} to look up 789 * String names for biomes, or construct your own table as you see fit (see docs in {@link SimpleBiomeMapper}). 790 */ 791 biomeCodeData; 792 793 @Override 794 public int getBiomeCode(int x, int y) { 795 return biomeCodeData[x][y]; 796 } 797 798 @Override 799 public int getHeatCode(int x, int y) { 800 return heatCodeData[x][y]; 801 } 802 803 @Override 804 public int getMoistureCode(int x, int y) { 805 return moistureCodeData[x][y]; 806 } 807 808 /** 809 * Gets a String array where biome codes can be used as indices to look up a name for the biome they refer to. 810 * This table uses 6 levels of heat and 6 levels of moisture, and tracks rivers, coastlines, lakes, and oceans 811 * as potentially different types of terrain. Biome codes can be obtained with {@link #getBiomeCode(int, int)}. 812 * This method returns a direct reference to {@link #biomeTable}, so modifying the returned array is discouraged 813 * (you should implement {@link BiomeMapper} using this class as a basis if you want to change its size). 814 * @return a direct reference to {@link #biomeTable}, a String array containing names of biomes 815 */ 816 @Override 817 public String[] getBiomeNameTable() { 818 return biomeTable; 819 } 820 821 public static final double 822 coldestValueLower = 0.0, coldestValueUpper = 0.15, // 0 823 colderValueLower = 0.15, colderValueUpper = 0.31, // 1 824 coldValueLower = 0.31, coldValueUpper = 0.5, // 2 825 warmValueLower = 0.5, warmValueUpper = 0.69, // 3 826 warmerValueLower = 0.69, warmerValueUpper = 0.85, // 4 827 warmestValueLower = 0.85, warmestValueUpper = 1.0, // 5 828 829 driestValueLower = 0.0, driestValueUpper = 0.27, // 0 830 drierValueLower = 0.27, drierValueUpper = 0.4, // 1 831 dryValueLower = 0.4, dryValueUpper = 0.6, // 2 832 wetValueLower = 0.6, wetValueUpper = 0.8, // 3 833 wetterValueLower = 0.8, wetterValueUpper = 0.9, // 4 834 wettestValueLower = 0.9, wettestValueUpper = 1.0; // 5 835 836 /** 837 * The default biome table to use with biome codes from {@link #biomeCodeData}. Biomes are assigned based on 838 * heat and moisture for the first 36 of 61 elements (coldest to warmest for each group of 6, with the first 839 * group as the dryest and the last group the wettest), then the next 6 are for coastlines (coldest to warmest), 840 * then rivers (coldest to warmest), then lakes (coldest to warmest), then oceans (coldest to warmest), and 841 * lastly a single "biome" for empty space outside the map (meant for projections that don't fill a rectangle). 842 */ 843 public static final String[] biomeTable = { 844 //COLDEST //COLDER //COLD //HOT //HOTTER //HOTTEST 845 "Ice", "Ice", "Grassland", "Desert", "Desert", "Desert", //DRYEST 846 "Ice", "Tundra", "Grassland", "Grassland", "Desert", "Desert", //DRYER 847 "Ice", "Tundra", "Woodland", "Woodland", "Savanna", "Desert", //DRY 848 "Ice", "Tundra", "SeasonalForest", "SeasonalForest", "Savanna", "Savanna", //WET 849 "Ice", "Tundra", "BorealForest", "TemperateRainforest", "TropicalRainforest", "Savanna", //WETTER 850 "Ice", "BorealForest", "BorealForest", "TemperateRainforest", "TropicalRainforest", "TropicalRainforest", //WETTEST 851 "Rocky", "Rocky", "Beach", "Beach", "Beach", "Beach", //COASTS 852 "Ice", "River", "River", "River", "River", "River", //RIVERS 853 "Ice", "River", "River", "River", "River", "River", //LAKES 854 "Ocean", "Ocean", "Ocean", "Ocean", "Ocean", "Ocean", //OCEAN 855 "Empty", //SPACE 856 }; 857 858 /** 859 * Simple constructor; pretty much does nothing. Make sure to call {@link #makeBiomes(WorldMapGenerator)} before 860 * using fields like {@link #biomeCodeData}. 861 */ 862 public SimpleBiomeMapper() 863 { 864 heatCodeData = null; 865 moistureCodeData = null; 866 biomeCodeData = null; 867 } 868 869 /** 870 * Analyzes the last world produced by the given WorldMapGenerator and uses all of its generated information to 871 * assign biome codes for each cell (along with heat and moisture codes). After calling this, biome codes can be 872 * taken from {@link #biomeCodeData} and used as indices into {@link #biomeTable} or a custom biome table. 873 * @param world a WorldMapGenerator that should have generated at least one map; it may be at any zoom 874 */ 875 @Override 876 public void makeBiomes(WorldMapGenerator world) { 877 if(world == null || world.width <= 0 || world.height <= 0) 878 return; 879 if(heatCodeData == null || (heatCodeData.length != world.width || heatCodeData[0].length != world.height)) 880 heatCodeData = new int[world.width][world.height]; 881 if(moistureCodeData == null || (moistureCodeData.length != world.width || moistureCodeData[0].length != world.height)) 882 moistureCodeData = new int[world.width][world.height]; 883 if(biomeCodeData == null || (biomeCodeData.length != world.width || biomeCodeData[0].length != world.height)) 884 biomeCodeData = new int[world.width][world.height]; 885 final double i_hot = (world.maxHeat == world.minHeat) ? 1.0 : 1.0 / (world.maxHeat - world.minHeat); 886 for (int x = 0; x < world.width; x++) { 887 for (int y = 0; y < world.height; y++) { 888 final double hot = (world.heatData[x][y] - world.minHeat) * i_hot, moist = world.moistureData[x][y]; 889 final int heightCode = world.heightCodeData[x][y]; 890 if(heightCode == 1000) { 891 biomeCodeData[x][y] = 60; 892 continue; 893 } 894 int hc, mc; 895 boolean isLake = false,// world.generateRivers && heightCode >= 4 && fresh > 0.65 && fresh + moist * 2.35 > 2.75,//world.partialLakeData.contains(x, y) && heightCode >= 4, 896 isRiver = false;// world.generateRivers && !isLake && heightCode >= 4 && fresh > 0.55 && fresh + moist * 2.2 > 2.15;//world.partialRiverData.contains(x, y) && heightCode >= 4; 897 if(heightCode < 4) { 898 mc = 9; 899 } 900 else if (moist > wetterValueUpper) { 901 mc = 5; 902 } else if (moist > wetValueUpper) { 903 mc = 4; 904 } else if (moist > dryValueUpper) { 905 mc = 3; 906 } else if (moist > drierValueUpper) { 907 mc = 2; 908 } else if (moist > driestValueUpper) { 909 mc = 1; 910 } else { 911 mc = 0; 912 } 913 914 if (hot > warmerValueUpper) { 915 hc = 5; 916 } else if (hot > warmValueUpper) { 917 hc = 4; 918 } else if (hot > coldValueUpper) { 919 hc = 3; 920 } else if (hot > colderValueUpper) { 921 hc = 2; 922 } else if (hot > coldestValueUpper) { 923 hc = 1; 924 } else { 925 hc = 0; 926 } 927 928 heatCodeData[x][y] = hc; 929 moistureCodeData[x][y] = mc; 930 // 54 == 9 * 6, 9 is used for Ocean groups 931 biomeCodeData[x][y] = heightCode < 4 ? hc + 54 // 54 == 9 * 6, 9 is used for Ocean groups 932 : isLake ? hc + 48 : heightCode == 4 ? hc + 36 : hc + mc * 6; 933 } 934 } 935 } 936 } 937 /** 938 * A way to get biome information for the cells on a map when you want an area's biome to be a combination of two 939 * main biome types, such as "Grassland" or "TropicalRainforest", with the biomes varying in weight between areas. 940 * <br> 941 * To use: 1, Construct a DetailedBiomeMapper (constructor takes no arguments). 2, call 942 * {@link #makeBiomes(WorldMapGenerator)} with a WorldMapGenerator that has already produced at least one world map. 943 * 3, get biome codes from the {@link #biomeCodeData} field, where a code is an int that can be used with the 944 * extract methods in this class to get various information from it (these are {@link #extractBiomeA(int)}, 945 * {@link #extractBiomeB(int)}, {@link #extractPartA(int)}, {@link #extractPartB(int)}, and 946 * {@link #extractMixAmount(int)}). You can get predefined names for biomes using the extractBiome methods (these 947 * names can be changed in {@link #biomeTable}), or raw indices into some (usually 61-element) collection or array 948 * with the extractPart methods. The extractMixAmount() method gets a float that is the amount by which biome B 949 * affects biome A; if this is higher than 0.5, then biome B is the "dominant" biome in the area. 950 */ 951 public static class DetailedBiomeMapper implements BiomeMapper 952 { 953 /** 954 * The heat codes for the analyzed map, from 0 to 5 inclusive, with 0 coldest and 5 hottest. 955 */ 956 public int[][] heatCodeData, 957 /** 958 * The moisture codes for the analyzed map, from 0 to 5 inclusive, with 0 driest and 5 wettest. 959 */ 960 moistureCodeData, 961 /** 962 * The biome codes for the analyzed map, using one int to store the codes for two biomes and the degree by which 963 * the second biome affects the first. These codes can be used with methods in this class like 964 * {@link #extractBiomeA(int)}, {@link #extractBiomeB(int)}, and {@link #extractMixAmount(int)} to find the two 965 * dominant biomes in an area, called biome A and biome B, and the mix amount, for finding how much biome B 966 * affects biome A. 967 */ 968 biomeCodeData; 969 970 971 /** 972 * Gets the biome code for the dominant biome at a given x,y position. This is equivalent to getting the raw 973 * biome code from {@link #biomeCodeData}, calling {@link #extractMixAmount(int)} on that raw biome code, and 974 * chooosing whether to call {@link #extractPartA(int)} or {@link #extractPartB(int)} based on whether the mix 975 * amount is lower than 0.5 (yielding part A) or higher (yielding part B). 976 * @param x the x-coordinate on the map 977 * @param y the y-coordinate on the map 978 * @return the biome code for the dominant biome part at the given location 979 */ 980 @Override 981 public int getBiomeCode(int x, int y) { 982 int code = biomeCodeData[x][y]; 983 if(code < 0x2000000) return code & 1023; 984 return (code >>> 10) & 1023; 985 } 986 987 @Override 988 public int getHeatCode(int x, int y) { 989 return heatCodeData[x][y]; 990 } 991 992 @Override 993 public int getMoistureCode(int x, int y) { 994 return moistureCodeData[x][y]; 995 } 996 997 /** 998 * Gets a String array where biome codes can be used as indices to look up a name for the biome they refer to. 999 * This table uses 6 levels of heat and 6 levels of moisture, and tracks rivers, coastlines, lakes, and oceans 1000 * as potentially different types of terrain. Biome codes can be obtained with {@link #getBiomeCode(int, int)}. 1001 * This method returns a direct reference to {@link #biomeTable}, so modifying the returned array is discouraged 1002 * (you should implement {@link BiomeMapper} using this class as a basis if you want to change its size). 1003 * @return a direct reference to {@link #biomeTable}, a String array containing names of biomes 1004 */ 1005 @Override 1006 public String[] getBiomeNameTable() { 1007 return biomeTable; 1008 } 1009 1010 public static final double 1011 coldestValueLower = 0.0, coldestValueUpper = 0.15, // 0 1012 colderValueLower = 0.15, colderValueUpper = 0.31, // 1 1013 coldValueLower = 0.31, coldValueUpper = 0.5, // 2 1014 warmValueLower = 0.5, warmValueUpper = 0.69, // 3 1015 warmerValueLower = 0.69, warmerValueUpper = 0.85, // 4 1016 warmestValueLower = 0.85, warmestValueUpper = 1.0, // 5 1017 1018 driestValueLower = 0.0, driestValueUpper = 0.27, // 0 1019 drierValueLower = 0.27, drierValueUpper = 0.4, // 1 1020 dryValueLower = 0.4, dryValueUpper = 0.6, // 2 1021 wetValueLower = 0.6, wetValueUpper = 0.8, // 3 1022 wetterValueLower = 0.8, wetterValueUpper = 0.9, // 4 1023 wettestValueLower = 0.9, wettestValueUpper = 1.0; // 5 1024 1025 /** 1026 * The default biome table to use with parts of biome codes from {@link #biomeCodeData}. Biomes are assigned by 1027 * heat and moisture for the first 36 of 61 elements (coldest to warmest for each group of 6, with the first 1028 * group as the dryest and the last group the wettest), then the next 6 are for coastlines (coldest to warmest), 1029 * then rivers (coldest to warmest), then lakes (coldest to warmest). The last is reserved for empty space. 1030 * <br> 1031 * Unlike with {@link SimpleBiomeMapper}, you cannot use a biome code directly from biomeCodeData as an index 1032 * into this in almost any case; you should pass the biome code to one of the extract methods. 1033 * {@link #extractBiomeA(int)} or {@link #extractBiomeB(int)} will work if you want a biome name, or 1034 * {@link #extractPartA(int)} or {@link #extractPartB(int)} should be used if you want a non-coded int that 1035 * represents one of the biomes' indices into something like this. You can also get the amount by which biome B 1036 * is affecting biome A with {@link #extractMixAmount(int)}. 1037 */ 1038 public static final String[] biomeTable = { 1039 //COLDEST //COLDER //COLD //HOT //HOTTER //HOTTEST 1040 "Ice", "Ice", "Grassland", "Desert", "Desert", "Desert", //DRYEST 1041 "Ice", "Tundra", "Grassland", "Grassland", "Desert", "Desert", //DRYER 1042 "Ice", "Tundra", "Woodland", "Woodland", "Savanna", "Desert", //DRY 1043 "Ice", "Tundra", "SeasonalForest", "SeasonalForest", "Savanna", "Savanna", //WET 1044 "Ice", "Tundra", "BorealForest", "TemperateRainforest", "TropicalRainforest", "Savanna", //WETTER 1045 "Ice", "BorealForest", "BorealForest", "TemperateRainforest", "TropicalRainforest", "TropicalRainforest", //WETTEST 1046 "Rocky", "Rocky", "Beach", "Beach", "Beach", "Beach", //COASTS 1047 "Ice", "River", "River", "River", "River", "River", //RIVERS 1048 "Ice", "River", "River", "River", "River", "River", //LAKES 1049 "Ocean", "Ocean", "Ocean", "Ocean", "Ocean", "Ocean", //OCEAN 1050 "Empty", //SPACE 1051 }; 1052 1053 /** 1054 * Gets the int stored in part A of the given biome code, which can be used as an index into other collections. 1055 * This int should almost always range from 0 to 60 (both inclusive), so collections this is used as an index 1056 * for should have a length of at least 61. 1057 * @param biomeCode a biome code that was probably received from {@link #biomeCodeData} 1058 * @return an int stored in the biome code's part A; almost always between 0 and 60, inclusive. 1059 */ 1060 public int extractPartA(int biomeCode) 1061 { 1062 return biomeCode & 1023; 1063 } 1064 /** 1065 * Gets a String from {@link #biomeTable} that names the appropriate biome in part A of the given biome code. 1066 * @param biomeCode a biome code that was probably received from {@link #biomeCodeData} 1067 * @return a String that names the biome in part A of biomeCode, or "Empty" if none can be found 1068 */ 1069 public String extractBiomeA(int biomeCode) 1070 { 1071 biomeCode &= 1023; 1072 if(biomeCode < 60) 1073 return biomeTable[biomeCode]; 1074 return "Empty"; 1075 } 1076 /** 1077 * Gets the int stored in part B of the given biome code, which can be used as an index into other collections. 1078 * This int should almost always range from 0 to 60 (both inclusive), so collections this is used as an index 1079 * for should have a length of at least 61. 1080 * @param biomeCode a biome code that was probably received from {@link #biomeCodeData} 1081 * @return an int stored in the biome code's part B; almost always between 0 and 60, inclusive. 1082 */ 1083 public int extractPartB(int biomeCode) 1084 { 1085 return (biomeCode >>> 10) & 1023; 1086 } 1087 1088 /** 1089 * Gets a String from {@link #biomeTable} that names the appropriate biome in part B of the given biome code. 1090 * @param biomeCode a biome code that was probably received from {@link #biomeCodeData} 1091 * @return a String that names the biome in part B of biomeCode, or "Ocean" if none can be found 1092 */ 1093 public String extractBiomeB(int biomeCode) 1094 { 1095 biomeCode = (biomeCode >>> 10) & 1023; 1096 if(biomeCode < 60) 1097 return biomeTable[biomeCode]; 1098 return "Empty"; 1099 } 1100 1101 /** 1102 * This gets the portion of a biome code that represents the amount of mixing between two biomes. 1103 * Biome codes are normally obtained from the {@link #biomeCodeData} field, and aren't very usable on their own 1104 * without calling methods like this, {@link #extractBiomeA(int)}, and {@link #extractBiomeB(int)}. This returns 1105 * a float between 0.0f (inclusive) and 1.0f (exclusive), with 0.0f meaning biome B has no effect on an area and 1106 * biome A is the only one used, 0.5f meaning biome A and biome B have equal effect, and 0.75f meaning biome B 1107 * has most of the effect, three-fourths of the area, and biome A has less, one-fourth of the area. 1108 * @param biomeCode a biome code that was probably received from {@link #biomeCodeData} 1109 * @return a float between 0.0f (inclusive) and 1.0f (exclusive) representing mixing of biome B into biome A 1110 */ 1111 public float extractMixAmount(int biomeCode) 1112 { 1113 return (biomeCode >>> 20) * 0x1p-10f; 1114 } 1115 1116 /** 1117 * Simple constructor; pretty much does nothing. Make sure to call {@link #makeBiomes(WorldMapGenerator)} before 1118 * using fields like {@link #biomeCodeData}. 1119 */ 1120 public DetailedBiomeMapper() 1121 { 1122 heatCodeData = null; 1123 moistureCodeData = null; 1124 biomeCodeData = null; 1125 } 1126 1127 /** 1128 * Analyzes the last world produced by the given WorldMapGenerator and uses all of its generated information to 1129 * assign biome codes for each cell (along with heat and moisture codes). After calling this, biome codes can be 1130 * taken from {@link #biomeCodeData} and used with methods in this class like {@link #extractBiomeA(int)}, 1131 * {@link #extractBiomeB(int)}, and {@link #extractMixAmount(int)} to find the two dominant biomes in an area, 1132 * called biome A and biome B, and the mix amount, for finding how much biome B affects biome A. 1133 * @param world a WorldMapGenerator that should have generated at least one map; it may be at any zoom 1134 */ 1135 @Override 1136 public void makeBiomes(WorldMapGenerator world) { 1137 if(world == null || world.width <= 0 || world.height <= 0) 1138 return; 1139 if(heatCodeData == null || (heatCodeData.length != world.width || heatCodeData[0].length != world.height)) 1140 heatCodeData = new int[world.width][world.height]; 1141 if(moistureCodeData == null || (moistureCodeData.length != world.width || moistureCodeData[0].length != world.height)) 1142 moistureCodeData = new int[world.width][world.height]; 1143 if(biomeCodeData == null || (biomeCodeData.length != world.width || biomeCodeData[0].length != world.height)) 1144 biomeCodeData = new int[world.width][world.height]; 1145 final int[][] heightCodeData = world.heightCodeData; 1146 final double[][] heatData = world.heatData, moistureData = world.moistureData, heightData = world.heightData; 1147 int hc, mc, heightCode, bc; 1148 double hot, moist, high, i_hot = 1.0 / world.maxHeat; 1149 for (int x = 0; x < world.width; x++) { 1150 for (int y = 0; y < world.height; y++) { 1151 1152 heightCode = heightCodeData[x][y]; 1153 if(heightCode == 1000) { 1154 biomeCodeData[x][y] = 60; 1155 continue; 1156 } 1157 hot = heatData[x][y]; 1158 moist = moistureData[x][y]; 1159 high = heightData[x][y]; 1160// fresh = world.freshwaterData[x][y]; 1161 boolean isLake = false,//world.generateRivers && heightCode >= 4 && fresh > 0.65 && fresh + moist * 2.35 > 2.75,//world.partialLakeData.contains(x, y) && heightCode >= 4, 1162 isRiver = false;//world.generateRivers && !isLake && heightCode >= 4 && fresh > 0.55 && fresh + moist * 2.2 > 2.15;//world.partialRiverData.contains(x, y) && heightCode >= 4; 1163 if (moist >= (wettestValueUpper - (wetterValueUpper - wetterValueLower) * 0.2)) { 1164 mc = 5; 1165 } else if (moist >= (wetterValueUpper - (wetValueUpper - wetValueLower) * 0.2)) { 1166 mc = 4; 1167 } else if (moist >= (wetValueUpper - (dryValueUpper - dryValueLower) * 0.2)) { 1168 mc = 3; 1169 } else if (moist >= (dryValueUpper - (drierValueUpper - drierValueLower) * 0.2)) { 1170 mc = 2; 1171 } else if (moist >= (drierValueUpper - (driestValueUpper) * 0.2)) { 1172 mc = 1; 1173 } else { 1174 mc = 0; 1175 } 1176 1177 if (hot >= (warmestValueUpper - (warmerValueUpper - warmerValueLower) * 0.2) * i_hot) { 1178 hc = 5; 1179 } else if (hot >= (warmerValueUpper - (warmValueUpper - warmValueLower) * 0.2) * i_hot) { 1180 hc = 4; 1181 } else if (hot >= (warmValueUpper - (coldValueUpper - coldValueLower) * 0.2) * i_hot) { 1182 hc = 3; 1183 } else if (hot >= (coldValueUpper - (colderValueUpper - colderValueLower) * 0.2) * i_hot) { 1184 hc = 2; 1185 } else if (hot >= (colderValueUpper - (coldestValueUpper) * 0.2) * i_hot) { 1186 hc = 1; 1187 } else { 1188 hc = 0; 1189 } 1190 1191 heatCodeData[x][y] = hc; 1192 moistureCodeData[x][y] = mc; 1193 // 54 == 9 * 6, 9 is used for Ocean groups 1194 bc = heightCode < 4 ? hc + 54 // 54 == 9 * 6, 9 is used for Ocean groups 1195 : isLake ? hc + 48 : heightCode == 4 ? hc + 36 : hc + mc * 6; 1196 1197 if(heightCode < 4) { 1198 mc = 9; 1199 } 1200 else if (moist >= (wetterValueUpper + (wettestValueUpper - wettestValueLower) * 0.2)) { 1201 mc = 5; 1202 } else if (moist >= (wetValueUpper + (wetterValueUpper - wetterValueLower) * 0.2)) { 1203 mc = 4; 1204 } else if (moist >= (dryValueUpper + (wetValueUpper - wetValueLower) * 0.2)) { 1205 mc = 3; 1206 } else if (moist >= (drierValueUpper + (dryValueUpper - dryValueLower) * 0.2)) { 1207 mc = 2; 1208 } else if (moist >= (driestValueUpper + (drierValueUpper - drierValueLower) * 0.2)) { 1209 mc = 1; 1210 } else { 1211 mc = 0; 1212 } 1213 1214 if (hot >= (warmerValueUpper + (warmestValueUpper - warmestValueLower) * 0.2) * i_hot) { 1215 hc = 5; 1216 } else if (hot >= (warmValueUpper + (warmerValueUpper - warmerValueLower) * 0.2) * i_hot) { 1217 hc = 4; 1218 } else if (hot >= (coldValueUpper + (warmValueUpper - warmValueLower) * 0.2) * i_hot) { 1219 hc = 3; 1220 } else if (hot >= (colderValueUpper + (coldValueUpper - coldValueLower) * 0.2) * i_hot) { 1221 hc = 2; 1222 } else if (hot >= (coldestValueUpper + (colderValueUpper - colderValueLower) * 0.2) * i_hot) { 1223 hc = 1; 1224 } else { 1225 hc = 0; 1226 } 1227 1228 bc |= (hc + mc * 6) << 10; 1229 if(heightCode < 4) 1230 biomeCodeData[x][y] = bc | (int)((heightData[x][y] + 1.0) * 1000.0) << 20; 1231 else biomeCodeData[x][y] = bc | (int) ((heightCode == 4) 1232 ? (sandUpper - high) * 10240.0 // multiplier affected by changes to sandLower 1233 : NumberTools.sway((high + moist) * (4.1 + high - hot)) * 512 + 512) << 20; 1234 } 1235 } 1236 } 1237 } 1238 1239 /** 1240 * A concrete implementation of {@link WorldMapGenerator} that tiles both east-to-west and north-to-south. It tends 1241 * to not appear distorted like {@link SphereMap} does in some areas, even though this is inaccurate for a 1242 * rectangular projection of a spherical world (that inaccuracy is likely what players expect in a map, though). 1243 * You may want {@link LocalMap} instead, for non-world maps that don't tile. 1244 * <a href="http://squidpony.github.io/SquidLib/DetailedWorldMapDemo.png" >Example map</a>. 1245 */ 1246 public static class TilingMap extends WorldMapGenerator { 1247 //protected static final double terrainFreq = 1.5, terrainRidgedFreq = 1.3, heatFreq = 2.8, moistureFreq = 2.9, otherFreq = 4.5; 1248// protected static final double terrainFreq = 1.175, terrainRidgedFreq = 1.3, heatFreq = 2.3, moistureFreq = 2.4, otherFreq = 3.5; 1249 protected static final double terrainFreq = 0.95, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375; 1250 private double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY, 1251 minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY, 1252 minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY; 1253 1254 public final Noise4D terrain, terrainRidged, heat, moisture, otherRidged; 1255 1256 /** 1257 * Constructs a concrete WorldMapGenerator for a map that can be used as a tiling, wrapping east-to-west as well 1258 * as north-to-south. Always makes a 256x256 map. 1259 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 1260 * If you were using {@link TilingMap#TilingMap(long, int, int, Noise4D, double)}, then this would be the 1261 * same as passing the parameters {@code 0x1337BABE1337D00DL, 256, 256, WorldMapGenerator.DEFAULT_NOISE, 1.0}. 1262 */ 1263 public TilingMap() { 1264 this(0x1337BABE1337D00DL, 256, 256, WorldMapGenerator.DEFAULT_NOISE, 1.0); 1265 } 1266 1267 /** 1268 * Constructs a concrete WorldMapGenerator for a map that can be used as a tiling, wrapping east-to-west as well 1269 * as north-to-south. 1270 * Takes only the width/height of the map. The initial seed is set to the same large long 1271 * every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and 1272 * height of the map cannot be changed after the fact, but you can zoom in. 1273 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 1274 * 1275 * @param mapWidth the width of the map(s) to generate; cannot be changed later 1276 * @param mapHeight the height of the map(s) to generate; cannot be changed later 1277 */ 1278 public TilingMap(int mapWidth, int mapHeight) { 1279 this(0x1337BABE1337D00DL, mapWidth, mapHeight, DEFAULT_NOISE, 1.0); 1280 } 1281 1282 /** 1283 * Constructs a concrete WorldMapGenerator for a map that can be used as a tiling, wrapping east-to-west as well 1284 * as north-to-south. 1285 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 1286 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 1287 * The width and height of the map cannot be changed after the fact, but you can zoom in. 1288 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 1289 * 1290 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 1291 * @param mapWidth the width of the map(s) to generate; cannot be changed later 1292 * @param mapHeight the height of the map(s) to generate; cannot be changed later 1293 */ 1294 public TilingMap(long initialSeed, int mapWidth, int mapHeight) { 1295 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, 1.0); 1296 } 1297 1298 /** 1299 * Constructs a concrete WorldMapGenerator for a map that can be used as a tiling, wrapping east-to-west as well 1300 * as north-to-south. Takes an initial seed, the width/height of the map, and a noise generator (a 1301 * {@link Noise4D} implementation, which is usually {@link FastNoise#instance}. The {@code initialSeed} 1302 * parameter may or may not be used, since you can specify the seed to use when you call 1303 * {@link #generate(long)}. The width and height of the map cannot be changed after the fact, but you can zoom 1304 * in. Any seed supplied to the Noise4D given to this (if it takes one) will be ignored, and 1305 * {@link Noise4D#getNoiseWithSeed(double, double, double, double, long)} will be used to specify the seed many 1306 * times. The detail level, which is the {@code octaveMultiplier} parameter that can be passed to another 1307 * constructor, is always 1.0 with this constructor. 1308 * 1309 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 1310 * @param mapWidth the width of the map(s) to generate; cannot be changed later 1311 * @param mapHeight the height of the map(s) to generate; cannot be changed later 1312 * @param noiseGenerator an instance of a noise generator capable of 4D noise, recommended to be {@link FastNoise#instance} 1313 */ 1314 public TilingMap(long initialSeed, int mapWidth, int mapHeight, final Noise4D noiseGenerator) { 1315 this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0); 1316 } 1317 1318 /** 1319 * Constructs a concrete WorldMapGenerator for a map that can be used as a tiling, wrapping east-to-west as well 1320 * as north-to-south. Takes an initial seed, the width/height of the map, and parameters for noise 1321 * generation (a {@link Noise4D} implementation, which is usually {@link FastNoise#instance}, and a 1322 * multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers 1323 * producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used, 1324 * since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map 1325 * cannot be changed after the fact, but you can zoom in. Any seed supplied to the Noise4D given to this (if it takes one) will be ignored, and 1326 * {@link Noise4D#getNoiseWithSeed(double, double, double, double, long)} will be used to specify the seed many 1327 * times. The {@code octaveMultiplier} parameter should probably be no lower than 0.5, but can be arbitrarily 1328 * high if you're willing to spend much more time on generating detail only noticeable at very high zoom; 1329 * normally 1.0 is fine and may even be too high for maps that don't require zooming. 1330 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 1331 * @param mapWidth the width of the map(s) to generate; cannot be changed later 1332 * @param mapHeight the height of the map(s) to generate; cannot be changed later 1333 * @param noiseGenerator an instance of a noise generator capable of 4D noise, almost always {@link FastNoise} 1334 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 1335 */ 1336 public TilingMap(long initialSeed, int mapWidth, int mapHeight, final Noise4D noiseGenerator, double octaveMultiplier) { 1337 super(initialSeed, mapWidth, mapHeight); 1338 terrain = new Noise.InverseLayered4D(noiseGenerator, (int) (0.5 + octaveMultiplier * 8), terrainFreq); 1339 terrainRidged = new Noise.Ridged4D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainRidgedFreq); 1340 heat = new Noise.InverseLayered4D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq); 1341 moisture = new Noise.InverseLayered4D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq); 1342 otherRidged = new Noise.Ridged4D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq); 1343 } 1344 1345 /** 1346 * Copies the TilingMap {@code other} to construct a new one that is exactly the same. References will only be 1347 * shared to Noise classes. 1348 * @param other a TilingMap to copy 1349 */ 1350 public TilingMap(TilingMap other) 1351 { 1352 super(other); 1353 terrain = other.terrain; 1354 terrainRidged = other.terrainRidged; 1355 heat = other.heat; 1356 moisture = other.moisture; 1357 otherRidged = other.otherRidged; 1358 minHeat0 = other.minHeat0; 1359 maxHeat0 = other.maxHeat0; 1360 minHeat1 = other.minHeat1; 1361 maxHeat1 = other.maxHeat1; 1362 minWet0 = other.minWet0; 1363 maxWet0 = other.maxWet0; 1364 } 1365 1366 protected void regenerate(int startX, int startY, int usedWidth, int usedHeight, 1367 double landMod, double heatMod, int stateA, int stateB) 1368 { 1369 boolean fresh = false; 1370 if(cacheA != stateA || cacheB != stateB || landMod != landModifier || heatMod != heatModifier) 1371 { 1372 minHeight = Double.POSITIVE_INFINITY; 1373 maxHeight = Double.NEGATIVE_INFINITY; 1374 minHeat0 = Double.POSITIVE_INFINITY; 1375 maxHeat0 = Double.NEGATIVE_INFINITY; 1376 minHeat1 = Double.POSITIVE_INFINITY; 1377 maxHeat1 = Double.NEGATIVE_INFINITY; 1378 minHeat = Double.POSITIVE_INFINITY; 1379 maxHeat = Double.NEGATIVE_INFINITY; 1380 minWet0 = Double.POSITIVE_INFINITY; 1381 maxWet0 = Double.NEGATIVE_INFINITY; 1382 minWet = Double.POSITIVE_INFINITY; 1383 maxWet = Double.NEGATIVE_INFINITY; 1384 cacheA = stateA; 1385 cacheB = stateB; 1386 fresh = true; 1387 } 1388 rng.setState(stateA, stateB); 1389 long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong(); 1390 int t; 1391 1392 landModifier = (landMod <= 0) ? rng.nextDouble(0.1875) + 0.99 : landMod; 1393 heatModifier = (heatMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : heatMod; 1394 1395 double p, q, 1396 ps, pc, 1397 qs, qc, 1398 h, temp, 1399 i_w = 6.283185307179586 / width, i_h = 6.283185307179586 / height, 1400 xPos = startX, yPos = startY, i_uw = usedWidth / (double)width, i_uh = usedHeight / (double)height; 1401 double[] trigTable = new double[width << 1]; 1402 for (int x = 0; x < width; x++, xPos += i_uw) { 1403 p = xPos * i_w; 1404 trigTable[x<<1] = NumberTools.sin(p); 1405 trigTable[x<<1|1] = NumberTools.cos(p); 1406 } 1407 for (int y = 0; y < height; y++, yPos += i_uh) { 1408 q = yPos * i_h; 1409 qs = NumberTools.sin(q); 1410 qc = NumberTools.cos(q); 1411 for (int x = 0, xt = 0; x < width; x++) { 1412 ps = trigTable[xt++];//NumberTools.sin(p); 1413 pc = trigTable[xt++];//NumberTools.cos(p); 1414 heightData[x][y] = (h = terrain.getNoiseWithSeed(pc + 1415 terrainRidged.getNoiseWithSeed(pc, ps, qc, qs,seedB - seedA) * 0.25, 1416 ps, qc, qs, seedA) + landModifier - 1.0); 1417 heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps, qc 1418 + otherRidged.getNoiseWithSeed(pc, ps, qc, qs, seedB + seedC) 1419 , qs, seedB)); 1420 moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qc, qs 1421 + otherRidged.getNoiseWithSeed(pc, ps, qc, qs, seedC + seedA) 1422 , seedC)); 1423 minHeightActual = Math.min(minHeightActual, h); 1424 maxHeightActual = Math.max(maxHeightActual, h); 1425 if(fresh) { 1426 minHeight = Math.min(minHeight, h); 1427 maxHeight = Math.max(maxHeight, h); 1428 1429 minHeat0 = Math.min(minHeat0, p); 1430 maxHeat0 = Math.max(maxHeat0, p); 1431 1432 minWet0 = Math.min(minWet0, temp); 1433 maxWet0 = Math.max(maxWet0, temp); 1434 1435 } 1436 } 1437 minHeightActual = Math.min(minHeightActual, minHeight); 1438 maxHeightActual = Math.max(maxHeightActual, maxHeight); 1439 1440 } 1441 double heightDiff = 2.0 / (maxHeightActual - minHeightActual), 1442 heatDiff = 0.8 / (maxHeat0 - minHeat0), 1443 wetDiff = 1.0 / (maxWet0 - minWet0), 1444 hMod, 1445 halfHeight = (height - 1) * 0.5, i_half = 1.0 / halfHeight; 1446 double minHeightActual0 = minHeightActual; 1447 double maxHeightActual0 = maxHeightActual; 1448 yPos = startY; 1449 ps = Double.POSITIVE_INFINITY; 1450 pc = Double.NEGATIVE_INFINITY; 1451 1452 for (int y = 0; y < height; y++, yPos += i_uh) { 1453 temp = Math.abs(yPos - halfHeight) * i_half; 1454 temp *= (2.4 - temp); 1455 temp = 2.2 - temp; 1456 for (int x = 0; x < width; x++) { 1457// heightData[x][y] = (h = (heightData[x][y] - minHeightActual) * heightDiff - 1.0); 1458// minHeightActual0 = Math.min(minHeightActual0, h); 1459// maxHeightActual0 = Math.max(maxHeightActual0, h); 1460 h = heightData[x][y]; 1461 heightCodeData[x][y] = (t = codeHeight(h)); 1462 hMod = 1.0; 1463 switch (t) { 1464 case 0: 1465 case 1: 1466 case 2: 1467 case 3: 1468 h = 0.4; 1469 hMod = 0.2; 1470 break; 1471 case 6: 1472 h = -0.1 * (h - forestLower - 0.08); 1473 break; 1474 case 7: 1475 h *= -0.25; 1476 break; 1477 case 8: 1478 h *= -0.4; 1479 break; 1480 default: 1481 h *= 0.05; 1482 } 1483 heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp); 1484 if (fresh) { 1485 ps = Math.min(ps, h); //minHeat0 1486 pc = Math.max(pc, h); //maxHeat0 1487 } 1488 } 1489 } 1490 if(fresh) 1491 { 1492 minHeat1 = ps; 1493 maxHeat1 = pc; 1494 } 1495 heatDiff = heatModifier / (maxHeat1 - minHeat1); 1496 qs = Double.POSITIVE_INFINITY; 1497 qc = Double.NEGATIVE_INFINITY; 1498 ps = Double.POSITIVE_INFINITY; 1499 pc = Double.NEGATIVE_INFINITY; 1500 1501 1502 for (int y = 0; y < height; y++) { 1503 for (int x = 0; x < width; x++) { 1504 heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff)); 1505 moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff); 1506 if (fresh) { 1507 qs = Math.min(qs, h); 1508 qc = Math.max(qc, h); 1509 ps = Math.min(ps, temp); 1510 pc = Math.max(pc, temp); 1511 } 1512 } 1513 } 1514 if(fresh) 1515 { 1516 minHeat = qs; 1517 maxHeat = qc; 1518 minWet = ps; 1519 maxWet = pc; 1520 } 1521 landData.refill(heightCodeData, 4, 999); 1522 /* 1523 if(generateRivers) { 1524 if (fresh) { 1525 addRivers(); 1526 riverData.connect8way().thin().thin(); 1527 lakeData.connect8way().thin(); 1528 partialRiverData.remake(riverData); 1529 partialLakeData.remake(lakeData); 1530 } else { 1531 partialRiverData.remake(riverData); 1532 partialLakeData.remake(lakeData); 1533 int stx = (zoomStartX >> (zoom)) - (width >> 1), 1534 sty = (zoomStartY >> (zoom)) - (height >> 1); 1535 for (int i = 1; i <= zoom; i++) { 1536// int stx = (startCacheX.get(i) - startCacheX.get(i - 1)) << (i - 1), 1537// sty = (startCacheY.get(i) - startCacheY.get(i - 1)) << (i - 1); 1538 if ((i & 3) == 3) { 1539 partialRiverData.zoom(stx, sty).connect8way(); 1540 partialRiverData.or(workingData.remake(partialRiverData).fringe().quasiRandomRegion(0.4)); 1541 partialLakeData.zoom(stx, sty).connect8way(); 1542 partialLakeData.or(workingData.remake(partialLakeData).fringe().quasiRandomRegion(0.55)); 1543 } else { 1544 partialRiverData.zoom(stx, sty).connect8way().thin(); 1545 partialRiverData.or(workingData.remake(partialRiverData).fringe().quasiRandomRegion(0.5)); 1546 partialLakeData.zoom(stx, sty).connect8way().thin(); 1547 partialLakeData.or(workingData.remake(partialLakeData).fringe().quasiRandomRegion(0.7)); 1548 } 1549 } 1550 } 1551 } 1552 */ 1553 } 1554 } 1555 1556 /** 1557 * A concrete implementation of {@link WorldMapGenerator} that distorts the map as it nears the poles, expanding the 1558 * smaller-diameter latitude lines in extreme north and south regions so they take up the same space as the equator; 1559 * this counteracts certain artifacts that are common in Simplex noise world maps by using a 4D noise call to 1560 * generate terrain, using a normal 3D noise call's result as the extra 4th dimension. This generator allows 1561 * choosing a {@link Noise3D}, which is used for most of the generation. This is ideal for projecting onto a 3D 1562 * sphere, which could squash the poles to counteract the stretch this does. You might also want to produce an oval 1563 * map that more-accurately represents the changes in the diameter of a latitude line on a spherical world; you 1564 * should use {@link EllipticalMap} or {@link EllipticalHammerMap} for this. 1565 * {@link HyperellipticalMap} is also a nice option because it can project onto a shape between a 1566 * rectangle (like this class) and an ellipse (like EllipticalMap), with all-round sides. 1567 * <a href="http://squidpony.github.io/SquidLib/SphereWorld.png" >Example map</a>. 1568 */ 1569 public static class SphereMap extends WorldMapGenerator { 1570 protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375; 1571 //protected static final double terrainFreq = 1.65, terrainRidgedFreq = 1.8, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375, riverRidgedFreq = 21.7; 1572 private double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY, 1573 minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY, 1574 minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY; 1575 1576 public final Noise3D terrain, heat, moisture, otherRidged, terrainLayered; 1577 public final double[][] xPositions, 1578 yPositions, 1579 zPositions; 1580 1581 1582 /** 1583 * Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a 1584 * 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to 1585 * have significantly-exaggerated-in-size features while the equator is not distorted. 1586 * Always makes a 256x128 map. 1587 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 1588 * If you were using {@link SphereMap#SphereMap(long, int, int, Noise3D, double)}, then this would be the 1589 * same as passing the parameters {@code 0x1337BABE1337D00DL, 256, 128, DEFAULT_NOISE, 1.0}. 1590 */ 1591 public SphereMap() { 1592 this(0x1337BABE1337D00DL, 256, 128, DEFAULT_NOISE, 1.0); 1593 } 1594 1595 /** 1596 * Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a 1597 * 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to 1598 * have significantly-exaggerated-in-size features while the equator is not distorted. 1599 * Takes only the width/height of the map. The initial seed is set to the same large long 1600 * every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and 1601 * height of the map cannot be changed after the fact, but you can zoom in. 1602 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 1603 * 1604 * @param mapWidth the width of the map(s) to generate; cannot be changed later 1605 * @param mapHeight the height of the map(s) to generate; cannot be changed later 1606 */ 1607 public SphereMap(int mapWidth, int mapHeight) { 1608 this(0x1337BABE1337D00DL, mapWidth, mapHeight, DEFAULT_NOISE,1.0); 1609 } 1610 1611 /** 1612 * Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a 1613 * 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to 1614 * have significantly-exaggerated-in-size features while the equator is not distorted. 1615 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 1616 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 1617 * The width and height of the map cannot be changed after the fact, but you can zoom in. 1618 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 1619 * 1620 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 1621 * @param mapWidth the width of the map(s) to generate; cannot be changed later 1622 * @param mapHeight the height of the map(s) to generate; cannot be changed later 1623 */ 1624 public SphereMap(long initialSeed, int mapWidth, int mapHeight) { 1625 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, 1.0); 1626 } 1627 1628 /** 1629 * Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a 1630 * 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to 1631 * have significantly-exaggerated-in-size features while the equator is not distorted. 1632 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 1633 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 1634 * The width and height of the map cannot be changed after the fact, but you can zoom in. 1635 * Uses FastNoise as its noise generator, with the given octave multiplier affecting detail. 1636 * 1637 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 1638 * @param mapWidth the width of the map(s) to generate; cannot be changed later 1639 * @param mapHeight the height of the map(s) to generate; cannot be changed later 1640 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 1641 */ 1642 public SphereMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) { 1643 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, octaveMultiplier); 1644 } 1645 1646 /** 1647 * Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a 1648 * 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to 1649 * have significantly-exaggerated-in-size features while the equator is not distorted. 1650 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 1651 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 1652 * The width and height of the map cannot be changed after the fact, but you can zoom in. 1653 * Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. 1654 * 1655 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 1656 * @param mapWidth the width of the map(s) to generate; cannot be changed later 1657 * @param mapHeight the height of the map(s) to generate; cannot be changed later 1658 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 1659 */ 1660 public SphereMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) { 1661 this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0); 1662 } 1663 1664 /** 1665 * Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a 1666 * 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to 1667 * have significantly-exaggerated-in-size features while the equator is not distorted. 1668 * Takes an initial seed, the width/height of the map, and parameters for noise 1669 * generation (a {@link Noise3D} implementation, which is usually {@link FastNoise#instance}, and a 1670 * multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers 1671 * producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used, 1672 * since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map 1673 * cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for 1674 * {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the 1675 * seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance} 1676 * because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should 1677 * probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on 1678 * generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps 1679 * that don't require zooming. 1680 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 1681 * @param mapWidth the width of the map(s) to generate; cannot be changed later 1682 * @param mapHeight the height of the map(s) to generate; cannot be changed later 1683 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise#instance} 1684 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 1685 */ 1686 public SphereMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier) { 1687 super(initialSeed, mapWidth, mapHeight); 1688 xPositions = new double[width][height]; 1689 yPositions = new double[width][height]; 1690 zPositions = new double[width][height]; 1691 1692 terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq); 1693 terrainLayered = new Noise.InverseLayered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325); 1694 heat = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75); 1695 moisture = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55); 1696 otherRidged = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq); 1697 } 1698 @Override 1699 public int wrapY(final int x, final int y) { 1700 return Math.max(0, Math.min(y, height - 1)); 1701 } 1702 1703 /** 1704 * Given a latitude and longitude in radians (the conventional way of describing points on a globe), this gets the 1705 * (x,y) Coord on the map projection this generator uses that corresponds to the given lat-lon coordinates. If this 1706 * generator does not represent a globe (if it is toroidal, for instance) or if there is no "good way" to calculate 1707 * the projection for a given lat-lon coordinate, this returns null. This implementation never returns null. 1708 * If this is a supported operation and the parameters are valid, this returns a Coord with x between 0 and 1709 * {@link #width}, and y between 0 and {@link #height}, both exclusive. Automatically wraps the Coord's values using 1710 * {@link #wrapX(int, int)} and {@link #wrapY(int, int)}. 1711 * @param latitude the latitude, from {@code Math.PI * -0.5} to {@code Math.PI * 0.5} 1712 * @param longitude the longitude, from {@code 0.0} to {@code Math.PI * 2.0} 1713 * @return the point at the given latitude and longitude, as a Coord with x between 0 and {@link #width} and y between 0 and {@link #height}, or null if unsupported 1714 */ 1715 // 0.7978845608028654 1.2533141373155001 1716 @Override 1717 public Coord project(double latitude, double longitude) { 1718 int x = (int)((((longitude - getCenterLongitude()) + 12.566370614359172) % 6.283185307179586) * 0.15915494309189535 * width), 1719 y = (int)((NumberTools.sin(latitude) * 0.5 + 0.5) * height); 1720 return Coord.get( 1721 wrapX(x, y), 1722 wrapY(x, y)); 1723 } 1724 1725 /** 1726 * Copies the SphereMap {@code other} to construct a new one that is exactly the same. References will only be 1727 * shared to Noise classes. 1728 * @param other a SphereMap to copy 1729 */ 1730 public SphereMap(SphereMap other) 1731 { 1732 super(other); 1733 terrain = other.terrain; 1734 terrainLayered = other.terrainLayered; 1735 heat = other.heat; 1736 moisture = other.moisture; 1737 otherRidged = other.otherRidged; 1738 minHeat0 = other.minHeat0; 1739 maxHeat0 = other.maxHeat0; 1740 minHeat1 = other.minHeat1; 1741 maxHeat1 = other.maxHeat1; 1742 minWet0 = other.minWet0; 1743 maxWet0 = other.maxWet0; 1744 xPositions = ArrayTools.copy(other.xPositions); 1745 yPositions = ArrayTools.copy(other.yPositions); 1746 zPositions = ArrayTools.copy(other.zPositions); 1747 } 1748 1749 protected void regenerate(int startX, int startY, int usedWidth, int usedHeight, 1750 double landMod, double heatMod, int stateA, int stateB) 1751 { 1752 boolean fresh = false; 1753 if(cacheA != stateA || cacheB != stateB || landMod != landModifier || heatMod != heatModifier) 1754 { 1755 minHeight = Double.POSITIVE_INFINITY; 1756 maxHeight = Double.NEGATIVE_INFINITY; 1757 minHeat0 = Double.POSITIVE_INFINITY; 1758 maxHeat0 = Double.NEGATIVE_INFINITY; 1759 minHeat1 = Double.POSITIVE_INFINITY; 1760 maxHeat1 = Double.NEGATIVE_INFINITY; 1761 minHeat = Double.POSITIVE_INFINITY; 1762 maxHeat = Double.NEGATIVE_INFINITY; 1763 minWet0 = Double.POSITIVE_INFINITY; 1764 maxWet0 = Double.NEGATIVE_INFINITY; 1765 minWet = Double.POSITIVE_INFINITY; 1766 maxWet = Double.NEGATIVE_INFINITY; 1767 cacheA = stateA; 1768 cacheB = stateB; 1769 fresh = true; 1770 } 1771 rng.setState(stateA, stateB); 1772 long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong(); 1773 int t; 1774 1775 landModifier = (landMod <= 0) ? rng.nextDouble(0.29) + 0.91 : landMod; 1776 heatModifier = (heatMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : heatMod; 1777 1778 double p, 1779 ps, pc, 1780 qs, qc, 1781 h, temp, 1782 i_w = 6.283185307179586 / width, i_h = 2.0 / (height+2.0),//(3.141592653589793) / (height+2.0), 1783 xPos = startX, yPos, i_uw = usedWidth / (double)width, i_uh = usedHeight * i_h / (height+2.0); 1784 final double[] trigTable = new double[width << 1]; 1785 for (int x = 0; x < width; x++, xPos += i_uw) { 1786 p = xPos * i_w + centerLongitude; 1787 // 0.7978845608028654 1.2533141373155001 1788 trigTable[x<<1] = NumberTools.sin(p);// * 1.2533141373155001; 1789 trigTable[x<<1|1] = NumberTools.cos(p);// * 0.7978845608028654; 1790 } 1791 yPos = startY * i_h + i_uh; 1792 for (int y = 0; y < height; y++, yPos += i_uh) { 1793 qs = -1 + yPos;//-1.5707963267948966 + yPos; 1794 qc = NumberTools.cos(NumberTools.asin(qs)); 1795 //qs = qs; 1796 //qs = NumberTools.sin(qs); 1797 for (int x = 0, xt = 0; x < width; x++) { 1798 ps = trigTable[xt++] * qc;//NumberTools.sin(p); 1799 pc = trigTable[xt++] * qc;//NumberTools.cos(p); 1800 xPositions[x][y] = pc; 1801 yPositions[x][y] = ps; 1802 zPositions[x][y] = qs; 1803 heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(pc + 1804 terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5, 1805 ps, qs, seedA) + landModifier - 1.0); 1806 heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps 1807 + otherRidged.getNoiseWithSeed(pc, ps, qs,seedB + seedC) 1808 , qs, seedB)); 1809 moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs 1810 + otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA) 1811 , seedC)); 1812 1813 minHeightActual = Math.min(minHeightActual, h); 1814 maxHeightActual = Math.max(maxHeightActual, h); 1815 if(fresh) { 1816 minHeight = Math.min(minHeight, h); 1817 maxHeight = Math.max(maxHeight, h); 1818 1819 minHeat0 = Math.min(minHeat0, p); 1820 maxHeat0 = Math.max(maxHeat0, p); 1821 1822 minWet0 = Math.min(minWet0, temp); 1823 maxWet0 = Math.max(maxWet0, temp); 1824 } 1825 } 1826 minHeightActual = Math.min(minHeightActual, minHeight); 1827 maxHeightActual = Math.max(maxHeightActual, maxHeight); 1828 1829 } 1830 double heatDiff = 0.8 / (maxHeat0 - minHeat0), 1831 wetDiff = 1.0 / (maxWet0 - minWet0), 1832 hMod; 1833 yPos = startY * i_h + i_uh; 1834 ps = Double.POSITIVE_INFINITY; 1835 pc = Double.NEGATIVE_INFINITY; 1836 1837 for (int y = 0; y < height; y++, yPos += i_uh) { 1838 temp = Math.abs(yPos - 1.0); 1839 temp *= (2.4 - temp); 1840 temp = 2.2 - temp; 1841 for (int x = 0; x < width; x++) { 1842// heightData[x][y] = (h = (heightData[x][y] - minHeightActual) * heightDiff - 1.0); 1843// minHeightActual0 = Math.min(minHeightActual0, h); 1844// maxHeightActual0 = Math.max(maxHeightActual0, h); 1845 h = heightData[x][y]; 1846 heightCodeData[x][y] = (t = codeHeight(h)); 1847 hMod = 1.0; 1848 switch (t) { 1849 case 0: 1850 case 1: 1851 case 2: 1852 case 3: 1853 h = 0.4; 1854 hMod = 0.2; 1855 break; 1856 case 6: 1857 h = -0.1 * (h - forestLower - 0.08); 1858 break; 1859 case 7: 1860 h *= -0.25; 1861 break; 1862 case 8: 1863 h *= -0.4; 1864 break; 1865 default: 1866 h *= 0.05; 1867 } 1868 heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp); 1869 if (fresh) { 1870 ps = Math.min(ps, h); //minHeat0 1871 pc = Math.max(pc, h); //maxHeat0 1872 } 1873 } 1874 } 1875 if(fresh) 1876 { 1877 minHeat1 = ps; 1878 maxHeat1 = pc; 1879 } 1880 heatDiff = heatModifier / (maxHeat1 - minHeat1); 1881 qs = Double.POSITIVE_INFINITY; 1882 qc = Double.NEGATIVE_INFINITY; 1883 ps = Double.POSITIVE_INFINITY; 1884 pc = Double.NEGATIVE_INFINITY; 1885 1886 1887 for (int y = 0; y < height; y++) { 1888 for (int x = 0; x < width; x++) { 1889 heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff)); 1890 moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff); 1891 if (fresh) { 1892 qs = Math.min(qs, h); 1893 qc = Math.max(qc, h); 1894 ps = Math.min(ps, temp); 1895 pc = Math.max(pc, temp); 1896 } 1897 } 1898 } 1899 if(fresh) 1900 { 1901 minHeat = qs; 1902 maxHeat = qc; 1903 minWet = ps; 1904 maxWet = pc; 1905 } 1906 landData.refill(heightCodeData, 4, 999); 1907 /* 1908 if(generateRivers) { 1909 if (fresh) { 1910 addRivers(); 1911 riverData.connect8way().thin().thin(); 1912 lakeData.connect8way().thin(); 1913 partialRiverData.remake(riverData); 1914 partialLakeData.remake(lakeData); 1915 } else { 1916 partialRiverData.remake(riverData); 1917 partialLakeData.remake(lakeData); 1918 int stx = Math.min(Math.max((zoomStartX >> zoom) - (width >> 2), 0), width), 1919 sty = Math.min(Math.max((zoomStartY >> zoom) - (height >> 2), 0), height); 1920 for (int i = 1; i <= zoom; i++) { 1921 int stx2 = (startCacheX.get(i) - startCacheX.get(i - 1)) << (i - 1), 1922 sty2 = (startCacheY.get(i) - startCacheY.get(i - 1)) << (i - 1); 1923 //(zoomStartX >> zoom) - (width >> 1 + zoom), (zoomStartY >> zoom) - (height >> 1 + zoom) 1924 1925// Map is 200x100, GreasedRegions have that size too. 1926// Zoom 0 only allows 100,50 as the center, 0,0 as the corner 1927// Zoom 1 allows 100,50 to 300,150 as the center (x2 coordinates), 0,0 to 200,100 (refers to 200,100) as the corner 1928// Zoom 2 allows 100,50 to 700,350 as the center (x4 coordinates), 0,0 to 200,100 (refers to 600,300) as the corner 1929 1930 1931 System.out.printf("zoomStartX: %d zoomStartY: %d, stx: %d sty: %d, stx2: %d, sty2: %d\n", zoomStartX, zoomStartY, stx, sty, stx2, sty2); 1932 if ((i & 3) == 3) { 1933 partialRiverData.zoom(stx, sty).connect8way(); 1934 partialRiverData.or(workingData.remake(partialRiverData).fringe().quasiRandomRegion(0.4)); 1935 partialLakeData.zoom(stx, sty).connect8way(); 1936 partialLakeData.or(workingData.remake(partialLakeData).fringe().quasiRandomRegion(0.55)); 1937 } else { 1938 partialRiverData.zoom(stx, sty).connect8way().thin(); 1939 partialRiverData.or(workingData.remake(partialRiverData).fringe().quasiRandomRegion(0.5)); 1940 partialLakeData.zoom(stx, sty).connect8way().thin(); 1941 partialLakeData.or(workingData.remake(partialLakeData).fringe().quasiRandomRegion(0.7)); 1942 } 1943 //stx = (width >> 1) ;//Math.min(Math.max(, 0), width); 1944 //sty = (height >> 1);//Math.min(Math.max(, 0), height); 1945 } 1946 System.out.println(); 1947 } 1948 } 1949 */ 1950 } 1951 } 1952 /** 1953 * A concrete implementation of {@link WorldMapGenerator} that projects the world map onto an ellipse that should be 1954 * twice as wide as it is tall (although you can stretch it by width and height that don't have that ratio). 1955 * This uses the <a href="https://en.wikipedia.org/wiki/Mollweide_projection">Mollweide projection</a>. 1956 * <a href="http://squidpony.github.io/SquidLib/EllipseWorld.png" >Example map</a>. 1957 */ 1958 public static class EllipticalMap extends WorldMapGenerator { 1959 // protected static final double terrainFreq = 1.35, terrainRidgedFreq = 1.8, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375, riverRidgedFreq = 21.7; 1960 protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375; 1961 protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY, 1962 minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY, 1963 minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY; 1964 1965 public final Noise3D terrain, heat, moisture, otherRidged, terrainLayered; 1966 public final double[][] xPositions, 1967 yPositions, 1968 zPositions; 1969 protected final int[] edges; 1970 1971 1972 /** 1973 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 1974 * ellipse without distortion of the sizes of features but with significant distortion of shape. 1975 * Always makes a 200x100 map. 1976 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 1977 * If you were using {@link EllipticalMap#EllipticalMap(long, int, int, Noise3D, double)}, then this would be the 1978 * same as passing the parameters {@code 0x1337BABE1337D00DL, 200, 100, DEFAULT_NOISE, 1.0}. 1979 */ 1980 public EllipticalMap() { 1981 this(0x1337BABE1337D00DL, 200, 100, DEFAULT_NOISE, 1.0); 1982 } 1983 1984 /** 1985 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 1986 * ellipse without distortion of the sizes of features but with significant distortion of shape. 1987 * Takes only the width/height of the map. The initial seed is set to the same large long 1988 * every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and 1989 * height of the map cannot be changed after the fact, but you can zoom in. 1990 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 1991 * 1992 * @param mapWidth the width of the map(s) to generate; cannot be changed later 1993 * @param mapHeight the height of the map(s) to generate; cannot be changed later 1994 */ 1995 public EllipticalMap(int mapWidth, int mapHeight) { 1996 this(0x1337BABE1337D00DL, mapWidth, mapHeight, DEFAULT_NOISE,1.0); 1997 } 1998 1999 /** 2000 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 2001 * ellipse without distortion of the sizes of features but with significant distortion of shape. 2002 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 2003 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 2004 * The width and height of the map cannot be changed after the fact, but you can zoom in. 2005 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 2006 * 2007 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 2008 * @param mapWidth the width of the map(s) to generate; cannot be changed later 2009 * @param mapHeight the height of the map(s) to generate; cannot be changed later 2010 */ 2011 public EllipticalMap(long initialSeed, int mapWidth, int mapHeight) { 2012 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, 1.0); 2013 } 2014 2015 /** 2016 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 2017 * ellipse without distortion of the sizes of features but with significant distortion of shape. 2018 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 2019 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 2020 * The width and height of the map cannot be changed after the fact, but you can zoom in. 2021 * Uses FastNoise as its noise generator, with the given octave multiplier affecting detail. 2022 * 2023 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 2024 * @param mapWidth the width of the map(s) to generate; cannot be changed later 2025 * @param mapHeight the height of the map(s) to generate; cannot be changed later 2026 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 2027 */ 2028 public EllipticalMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) { 2029 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, octaveMultiplier); 2030 } 2031 2032 /** 2033 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 2034 * ellipse without distortion of the sizes of features but with significant distortion of shape. 2035 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 2036 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 2037 * The width and height of the map cannot be changed after the fact, but you can zoom in. 2038 * Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. The suggested Noise3D 2039 * implementation to use is {@link FastNoise#instance}. 2040 * 2041 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 2042 * @param mapWidth the width of the map(s) to generate; cannot be changed later 2043 * @param mapHeight the height of the map(s) to generate; cannot be changed later 2044 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 2045 */ 2046 public EllipticalMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) { 2047 this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0); 2048 } 2049 2050 /** 2051 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 2052 * ellipse without distortion of the sizes of features but with significant distortion of shape. 2053 * Takes an initial seed, the width/height of the map, and parameters for noise generation (a 2054 * {@link Noise3D} implementation, where {@link FastNoise#instance} is suggested, and a 2055 * multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers 2056 * producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used, 2057 * since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map 2058 * cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for 2059 * {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the 2060 * seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance} 2061 * because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should 2062 * probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on 2063 * generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps 2064 * that don't require zooming. 2065 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 2066 * @param mapWidth the width of the map(s) to generate; cannot be changed later 2067 * @param mapHeight the height of the map(s) to generate; cannot be changed later 2068 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 2069 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 2070 */ 2071 public EllipticalMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier) { 2072 super(initialSeed, mapWidth, mapHeight); 2073 xPositions = new double[width][height]; 2074 yPositions = new double[width][height]; 2075 zPositions = new double[width][height]; 2076 edges = new int[height << 1]; 2077 terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq); 2078 terrainLayered = new Noise.InverseLayered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325); 2079 heat = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75); 2080 moisture = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55); 2081 otherRidged = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq); 2082 } 2083 2084 /** 2085 * Copies the EllipticalMap {@code other} to construct a new one that is exactly the same. References will only 2086 * be shared to Noise classes. 2087 * @param other an EllipticalMap to copy 2088 */ 2089 public EllipticalMap(EllipticalMap other) 2090 { 2091 super(other); 2092 terrain = other.terrain; 2093 terrainLayered = other.terrainLayered; 2094 heat = other.heat; 2095 moisture = other.moisture; 2096 otherRidged = other.otherRidged; 2097 minHeat0 = other.minHeat0; 2098 maxHeat0 = other.maxHeat0; 2099 minHeat1 = other.minHeat1; 2100 maxHeat1 = other.maxHeat1; 2101 minWet0 = other.minWet0; 2102 maxWet0 = other.maxWet0; 2103 xPositions = ArrayTools.copy(other.xPositions); 2104 yPositions = ArrayTools.copy(other.yPositions); 2105 zPositions = ArrayTools.copy(other.zPositions); 2106 edges = Arrays.copyOf(other.edges, other.edges.length); 2107 } 2108 2109 @Override 2110 public int wrapX(final int x, int y) { 2111 y = Math.max(0, Math.min(y, height - 1)); 2112 if(x < edges[y << 1]) 2113 return edges[y << 1 | 1]; 2114 else if(x > edges[y << 1 | 1]) 2115 return edges[y << 1]; 2116 else return x; 2117 } 2118 2119 @Override 2120 public int wrapY(final int x, final int y) { 2121 return Math.max(0, Math.min(y, height - 1)); 2122 } 2123 2124 protected void regenerate(int startX, int startY, int usedWidth, int usedHeight, 2125 double landMod, double heatMod, int stateA, int stateB) 2126 { 2127 boolean fresh = false; 2128 if(cacheA != stateA || cacheB != stateB || landMod != landModifier || heatMod != heatModifier) 2129 { 2130 minHeight = Double.POSITIVE_INFINITY; 2131 maxHeight = Double.NEGATIVE_INFINITY; 2132 minHeat0 = Double.POSITIVE_INFINITY; 2133 maxHeat0 = Double.NEGATIVE_INFINITY; 2134 minHeat1 = Double.POSITIVE_INFINITY; 2135 maxHeat1 = Double.NEGATIVE_INFINITY; 2136 minHeat = Double.POSITIVE_INFINITY; 2137 maxHeat = Double.NEGATIVE_INFINITY; 2138 minWet0 = Double.POSITIVE_INFINITY; 2139 maxWet0 = Double.NEGATIVE_INFINITY; 2140 minWet = Double.POSITIVE_INFINITY; 2141 maxWet = Double.NEGATIVE_INFINITY; 2142 cacheA = stateA; 2143 cacheB = stateB; 2144 fresh = true; 2145 } 2146 rng.setState(stateA, stateB); 2147 long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong(); 2148 int t; 2149 2150 landModifier = (landMod <= 0) ? rng.nextDouble(0.2) + 0.91 : landMod; 2151 heatModifier = (heatMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : heatMod; 2152 2153 double p, 2154 ps, pc, 2155 qs, qc, 2156 h, temp, yPos, xPos, 2157 i_uw = usedWidth / (double)width, 2158 i_uh = usedHeight / (double)height, 2159 th, thx, thy, lon, lat, ipi = 0.99999 / Math.PI, 2160 rx = width * 0.25, irx = 1.0 / rx, hw = width * 0.5, 2161 ry = height * 0.5, iry = 1.0 / ry; 2162 2163 yPos = startY - ry; 2164 for (int y = 0; y < height; y++, yPos += i_uh) { 2165 thx = NumberTools.asin((yPos) * iry); 2166 lon = (thx == Math.PI * 0.5 || thx == Math.PI * -0.5) ? thx : Math.PI * irx * 0.5 / NumberTools.cos(thx); 2167 thy = thx * 2.0; 2168 lat = NumberTools.asin((thy + NumberTools.sin(thy)) * ipi); 2169 2170 qc = NumberTools.cos(lat); 2171 qs = NumberTools.sin(lat); 2172 2173 boolean inSpace = true; 2174 xPos = startX; 2175 for (int x = 0; x < width; x++, xPos += i_uw) { 2176 th = lon * (xPos - hw); 2177 if(th < -3.141592653589793 || th > 3.141592653589793) { 2178 heightCodeData[x][y] = 10000; 2179 inSpace = true; 2180 continue; 2181 } 2182 if(inSpace) 2183 { 2184 inSpace = false; 2185 edges[y << 1] = x; 2186 } 2187 edges[y << 1 | 1] = x; 2188 th += centerLongitude; 2189 ps = NumberTools.sin(th) * qc; 2190 pc = NumberTools.cos(th) * qc; 2191 xPositions[x][y] = pc; 2192 yPositions[x][y] = ps; 2193 zPositions[x][y] = qs; 2194 heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(pc + 2195 terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5, 2196 ps, qs, seedA) + landModifier - 1.0); 2197 heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps 2198 + otherRidged.getNoiseWithSeed(pc, ps, qs,seedB + seedC) 2199 , qs, seedB)); 2200 moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs 2201 + otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA) 2202 , seedC)); 2203 minHeightActual = Math.min(minHeightActual, h); 2204 maxHeightActual = Math.max(maxHeightActual, h); 2205 if(fresh) { 2206 minHeight = Math.min(minHeight, h); 2207 maxHeight = Math.max(maxHeight, h); 2208 2209 minHeat0 = Math.min(minHeat0, p); 2210 maxHeat0 = Math.max(maxHeat0, p); 2211 2212 minWet0 = Math.min(minWet0, temp); 2213 maxWet0 = Math.max(maxWet0, temp); 2214 } 2215 } 2216 minHeightActual = Math.min(minHeightActual, minHeight); 2217 maxHeightActual = Math.max(maxHeightActual, maxHeight); 2218 2219 } 2220 double heatDiff = 0.8 / (maxHeat0 - minHeat0), 2221 wetDiff = 1.0 / (maxWet0 - minWet0), 2222 hMod, 2223 halfHeight = (height - 1) * 0.5, i_half = 1.0 / halfHeight; 2224 yPos = startY + i_uh; 2225 ps = Double.POSITIVE_INFINITY; 2226 pc = Double.NEGATIVE_INFINITY; 2227 2228 for (int y = 0; y < height; y++, yPos += i_uh) { 2229 temp = Math.abs(yPos - halfHeight) * i_half; 2230 temp *= (2.4 - temp); 2231 temp = 2.2 - temp; 2232 for (int x = 0; x < width; x++) { 2233 h = heightData[x][y]; 2234 if(heightCodeData[x][y] == 10000) { 2235 heightCodeData[x][y] = 1000; 2236 continue; 2237 } 2238 else 2239 heightCodeData[x][y] = (t = codeHeight(h)); 2240 hMod = 1.0; 2241 switch (t) { 2242 case 0: 2243 case 1: 2244 case 2: 2245 case 3: 2246 h = 0.4; 2247 hMod = 0.2; 2248 break; 2249 case 6: 2250 h = -0.1 * (h - forestLower - 0.08); 2251 break; 2252 case 7: 2253 h *= -0.25; 2254 break; 2255 case 8: 2256 h *= -0.4; 2257 break; 2258 default: 2259 h *= 0.05; 2260 } 2261 heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp); 2262 if (fresh) { 2263 ps = Math.min(ps, h); //minHeat0 2264 pc = Math.max(pc, h); //maxHeat0 2265 } 2266 } 2267 } 2268 if(fresh) 2269 { 2270 minHeat1 = ps; 2271 maxHeat1 = pc; 2272 } 2273 heatDiff = heatModifier / (maxHeat1 - minHeat1); 2274 qs = Double.POSITIVE_INFINITY; 2275 qc = Double.NEGATIVE_INFINITY; 2276 ps = Double.POSITIVE_INFINITY; 2277 pc = Double.NEGATIVE_INFINITY; 2278 2279 2280 for (int y = 0; y < height; y++) { 2281 for (int x = 0; x < width; x++) { 2282 heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff)); 2283 moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff); 2284 if (fresh) { 2285 qs = Math.min(qs, h); 2286 qc = Math.max(qc, h); 2287 ps = Math.min(ps, temp); 2288 pc = Math.max(pc, temp); 2289 } 2290 } 2291 } 2292 if(fresh) 2293 { 2294 minHeat = qs; 2295 maxHeat = qc; 2296 minWet = ps; 2297 maxWet = pc; 2298 } 2299 landData.refill(heightCodeData, 4, 999); 2300 } 2301 } 2302 2303 /** 2304 * An unusual map generator that imitates an existing map (such as a map of Earth, which it can do by default). It 2305 * uses the Mollweide projection (an elliptical map projection, the same as what EllipticalMap uses) for both its 2306 * input and output; <a href="https://squidpony.github.io/SquidLib/MimicWorld.png">an example can be seen here</a>, 2307 * imitating Earth using a 512x256 world map as a GreasedRegion for input. 2308 */ 2309 public static class MimicMap extends EllipticalMap 2310 { 2311 public GreasedRegion earth; 2312 public GreasedRegion shallow; 2313 public GreasedRegion coast; 2314 public GreasedRegion earthOriginal; 2315 /** 2316 * Constructs a concrete WorldMapGenerator for a map that should look like Earth using an elliptical projection 2317 * (specifically, a Mollweide projection). 2318 * Always makes a 512x256 map. 2319 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 2320 * If you were using {@link MimicMap#MimicMap(long, Noise3D, double)}, then this would be the 2321 * same as passing the parameters {@code 0x1337BABE1337D00DL, DEFAULT_NOISE, 1.0}. 2322 */ 2323 public MimicMap() { 2324 this(0x1337BABE1337D00DL 2325 , DEFAULT_NOISE, 1.0); 2326 } 2327 2328 /** 2329 * Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the 2330 * given GreasedRegion's "on" cells, using an elliptical projection (specifically, a Mollweide projection). 2331 * The initial seed is set to the same large long every time, and it's likely that you would set the seed when 2332 * you call {@link #generate(long)}. The width and height of the map cannot be changed after the fact. 2333 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 2334 * 2335 * @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied 2336 */ 2337 public MimicMap(GreasedRegion toMimic) { 2338 this(0x1337BABE1337D00DL, toMimic, DEFAULT_NOISE,1.0); 2339 } 2340 2341 /** 2342 * Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the 2343 * given GreasedRegion's "on" cells, using an elliptical projection (specifically, a Mollweide projection). 2344 * Takes an initial seed and the GreasedRegion containing land positions. The {@code initialSeed} 2345 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 2346 * The width and height of the map cannot be changed after the fact. 2347 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 2348 * 2349 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 2350 * @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied 2351 */ 2352 public MimicMap(long initialSeed, GreasedRegion toMimic) { 2353 this(initialSeed, toMimic, DEFAULT_NOISE, 1.0); 2354 } 2355 2356 /** 2357 * Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the 2358 * given GreasedRegion's "on" cells, using an elliptical projection (specifically, a Mollweide projection). 2359 * Takes an initial seed, the GreasedRegion containing land positions, and a multiplier that affects the level 2360 * of detail by increasing or decreasing the number of octaves of noise used. The {@code initialSeed} 2361 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 2362 * The width and height of the map cannot be changed after the fact. 2363 * Uses FastNoise as its noise generator, with the given octave multiplier affecting detail. 2364 * 2365 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 2366 * @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied 2367 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 2368 */ 2369 public MimicMap(long initialSeed, GreasedRegion toMimic, double octaveMultiplier) { 2370 this(initialSeed, toMimic, DEFAULT_NOISE, octaveMultiplier); 2371 } 2372 2373 /** 2374 * Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the 2375 * given GreasedRegion's "on" cells, using an elliptical projection (specifically, a Mollweide projection). 2376 * Takes an initial seed, the GreasedRegion containing land positions, and parameters for noise generation (a 2377 * {@link Noise3D} implementation, which is usually {@link FastNoise#instance}. The {@code initialSeed} 2378 * parameter may or may not be used, since you can specify the seed to use when you call 2379 * {@link #generate(long)}. The width and height of the map cannot be changed after the fact. Both FastNoise 2380 * and FastNoise make sense to use for {@code noiseGenerator}, and the seed it's constructed with doesn't matter 2381 * because this will change the seed several times at different scales of noise (it's fine to use the static 2382 * {@link FastNoise#instance} or {@link FastNoise#instance} because they have no changing state between runs 2383 * of the program). Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. 2384 * 2385 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 2386 * @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied 2387 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} or {@link FastNoise} 2388 */ 2389 public MimicMap(long initialSeed, GreasedRegion toMimic, Noise3D noiseGenerator) { 2390 this(initialSeed, toMimic, noiseGenerator, 1.0); 2391 } 2392 2393 /** 2394 * Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the 2395 * given GreasedRegion's "on" cells, using an elliptical projection (specifically, a Mollweide projection). 2396 * Takes an initial seed, the GreasedRegion containing land positions, parameters for noise generation (a 2397 * {@link Noise3D} implementation, which is usually {@link FastNoise#instance}, and a multiplier on how many 2398 * octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers producing even more 2399 * detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used, 2400 * since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map 2401 * cannot be changed after the fact. FastNoise will be the fastest 3D generator to use for 2402 * {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the 2403 * seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance} 2404 * because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should 2405 * probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on 2406 * generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps 2407 * that don't require zooming. 2408 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 2409 * @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied 2410 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} or {@link FastNoise} 2411 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 2412 */ 2413 public MimicMap(long initialSeed, GreasedRegion toMimic, Noise3D noiseGenerator, double octaveMultiplier) { 2414 super(initialSeed, toMimic.width, toMimic.height, noiseGenerator, octaveMultiplier); 2415 earth = toMimic; 2416 earthOriginal = earth.copy(); 2417 coast = earth.copy().not().fringe(2); 2418 shallow = earth.copy().fringe(2); 2419 } 2420 2421 /** 2422 * Constructs a 512x256 elliptical world map that will use land forms with a similar shape to Earth. 2423 * @param initialSeed 2424 * @param noiseGenerator 2425 * @param octaveMultiplier 2426 */ 2427 public MimicMap(long initialSeed, Noise3D noiseGenerator, double octaveMultiplier) 2428 { 2429 this(initialSeed, 2430 GreasedRegion.decompress(LZSPlus.decompress( 2431 "Ƥ䊅⑃л䣁ᩡġˤȰࠨ⑀ၰ䁶ިШဵ⃠䁠ႠʹƨЬࠨư්洤㘢啰ీᆕߊ偌怫本\u242AФd˄д.㖈่ը˨ըᠥࠨ䠤㐪䠼ࠬ栶ࠨଈڇ䎵兊Ƅ䊡ᒠ䒩Ԣ㤠ᬡ䴡䵒خ㐯簤ᐫ䐤ᐸŨϤሡ囁䓈䂔⁓ၯࠥ吥ᨡ儡⡖䁚⁊ဪ䠤⑥❺ᇕ粨䁓灜儯ُⓤϡǒᛤ䪆ୀᅔь掭稡缡ত䁲⁀Ұ\u0EA8䐢娠洩们䁔恄⍠⡨٤̭डಠ䉠㻀ᔲ䱍㐡ᨢࡕ\u089C䱢ⶀՐO廴∣℡䳕䂊⁊ှ栥ᦅي儠\u1CA0䣴⁐䠰ܨդϢü╏䑠䒤䉀མҫ于◧вႠ㥀ᑈȲl\u1CB4䄶ヘ䈩LJᅂ䈶┵℠湠㖰դʈ甤嫤利በڰl±ì\u2029䠩ন娼㠇砩\u175F䌬\u1AD9䱠ྰɔıT戽㑢£䁔ྸ䍂㣠ᑰඅ℠䥠∳ԋ棸玲䑂m䀣專*ơ噓NJေ㩈囤Ŵ䄈㤴ះ۸†䠨䫀纆ဣ䐣冸楚ಫ牴歀ઈdž傠Ѩᴮ㨡䊠涻䢤ȑ÷ᚵР泡⡠௰ס嫼吠㪤ȃÿ\u202D䐪䅅㸵ᅨɶIJ䑀وւ夸‾⠪䈡ᑠ䉀ඈƀҨ㧃⌂¶䁼唨Ö䄈䨡ࢤŵì\u3130̈́Ƞɠ癀ۨã㺨㥓ⱂÏဳ᠊䈡㱠ీ塃\u1754㰭䪁ᜂ䂸⺑扬䈫䱥紤⣩䭲倩⊂Z怦ݠᩝᑡ*瀲⋒瘱Ƞ❠㨭㧳ᜩ䕕⫣瘈♨‿⨮㧌℧懢ච\u20C3ᗨƉ¿媖ഞ䛤\u08C9ᢅ䪤ᘬ¸䁰䠢礠䐿揠槌С喢ጫ™䈹ਠр㴬懯\u2028䰣惤ኰ悠ᄬDzだഘقɀ䋑Ɏ♡Wࠦ掲ฺ戠\u2E50ম峼\u2DA7掸㦨ŀ䁹༠墡ဣ⤠孠أő䉚Р㊠⿹䍂玳ɀ䀣ᴧ⁘‥ᐡ戶ႤÏ䁴\u0D50䲚Ⲫ嚆\u0E91㢙䠒ၠĈր婠煍㼱\u0B91^瀥˅Ɂᓺ䈸↞ቘ⛀䀻堮㔒怮吡カ孧䉊偦IJ兄ø䁴Ǧ䡂ࠃࠩ哃互⠠¡䂮吡䓠縸䝐䀨⠨⇆䦀䖹᪈ÃĈׄ⢤ᕶౄಣ⺉㊧₠ᥰ൘塠Ǹݡ〥浑\"倭ᔂ⓽Ƞ䃠≩䔠匄䬨ㆸ䆆\u1942ⱋ㻭ぷ‡簥碯唥\u08C6\u0A64梴㉄Ë䂇Ւ䔮३昳ࠡ㒠㚤溁崴䛭࿖暁:㠭ዀ戭୮ጣȤ慤掝ᦵŒያ\u20C1ᛶジ♱ႅ硧ࢶ㦰䐂憰ı¤ڨ⌅╳呬ᢌ戚怨Ⱕ\u20FFΣᄈ၃◶Ӑ\u0DE5㪁ᦠ\u0890ઢv‼曬渢瑨ᝤກ磒ᵡ婕沱2䐣厜䀿䠮䃸䪨ƿ\u20C8ʬõ䀻≒䱠ঘʱ≔憘㙀ĂÖ劦惐䀸䠤樞ȍ䄤•愢尳㸊ņຢ梠ްઃ串ṹ⑮檢\u0080怺⌁昵㣨䩗ᐩ䑁Ӗ䑱䛭ᵁС㚠䐰ƃpႨ³èᄤė䁴̔癤䜳㹢\u0FF7樆ࠢ傡栤櫐ಐ䐠䪠ዬႸ䀴㠨℠㧀ၤm堡ࠡठТ㢩ᑀۜ̏房㽋欦㛺㳘召‧℡\u08CCन㨤T࡚Ɛ‡樠ໟ恥₡祤䖄ᘦ峤߱嘰Ƴdʬ㷨䇆ᢐ䱀\u008D~ᰁ灕姆ᬗ䊟Ϣ器⋸ශᡮ㈚伩墵凑᪂穅ڼ\u2D28Å䁃唀ᵧၐ䑡+䠧ᢄഐᨻȖၣ⸩㈠⭙ᔢ\"〭愞ண澮ጪュ湠䆀央刁ડⲿや⻁\u242A檈ᜧ沋坘㗠籽⢰缱ؘȒ&堵ĠᏰࡡ䠴ᆊա籖!⬂屴Ѳ䁊䑖ס࠹䢑ᮣ砷凬‧删ิ㮜ᚠヴР棠⨡ᴾム㩩ნ楨аƍ䂤ٸ榣䅕ᰃ両ȁؠ猨㎸㉪恉ᡀⒹ䁒⬰灁Ƞ᳀ዉົ࡞◢⡔݀猊䄉䯐砻ƨހ䭓拝℥䁄ཾమ䢠ʤʸ⻤ჴࡁ#Э䢠ਔ۠㟣を@奟椨ވ\u08CF䏌⩶晠Ϻİ㏤U⁄‰┣㈡Ð硭ウੁ9䠧䂠\u0B84˄ⶃ傠ňթ吡⻈䡡⭹䆤Ꮓ慀ʞĊ䫡灀ȊŰဣ◠㘫ᗌ॰ᅓƯḃㅤɴ䀨∦䱜Դጯዤࠠ嶠ᛨu䂌䘪ᬮ᪴ˠฦ凞ᘂ牀͡→ᗑ㦖⁘ំ墉䞤ଫ婤݁࠺ુ屣㉇撱\u0CF4制児䉍・䶁ཏĠӐի䅩礠ᶨ\u0FE0ॸ儠‚\u0080殯䀳儢B㠠ᔨు屏䈠ጤڥ灋挲∥咂դ⩦↩୵䙏ᑐ\u1CB1ⵟ⋀‡璠斅䒱☤Ś̔亩䈒塁#⠧椐⇜沱Ġనӊᠹ焘༢W瀲\u0A78ဠ⊠ع䄞ᠫ㸢ℽჵࡤ֊⾼Ⴏᔁⶫ⊮婕ࡀϒÚ⥢倴皏ⵦ璌犰䅬ᣠ€碵Ġːऺ缨椴ၲ㞱櫂⺵⁁勠含めᣜ紧悌ྷ䱻\u0088᭄⹅ⓖ琸䢢ฐ㞹䄠ጨ\u0BA1瓣䂫Ⴂ爩㈠ႈႡኄ∠㏨Ẋ\u3040ΡȘဢ㬡䔱ଥΦ၀.łඪ≂ʬㄠ坤ۼ懱㖢Ѹ籤憗ၠ楑\u0BE2•䜠檷┶ᨧ瀫\u0560䀦ɦٴۘ䈢䠶ߍ娴℠ࣰฤ琫ᕎ\u0AB1ᇀ䄠 " 2432 )), 2433 noiseGenerator, octaveMultiplier); 2434 } 2435 2436 /** 2437 * Copies the MimicMap {@code other} to construct a new one that is exactly the same. References will only 2438 * be shared to Noise classes. 2439 * @param other a MimicMap to copy 2440 */ 2441 public MimicMap(MimicMap other) 2442 { 2443 super(other); 2444 earth = other.earth.copy(); 2445 earthOriginal = other.earthOriginal.copy(); 2446 coast = other.coast.copy(); 2447 shallow = other.shallow.copy(); 2448 } 2449 2450 2451 2452 /** 2453 * Meant for making maps conform to the Mollweide (elliptical) projection that MimicMap uses. 2454 * @param rectangular A GreasedRegion where "on" represents land and "off" water, using any rectangular projection 2455 * @return a reprojected version of {@code rectangular} that uses an elliptical projection 2456 */ 2457 public static GreasedRegion reprojectToElliptical(GreasedRegion rectangular) { 2458 int width = rectangular.width, height = rectangular.height; 2459 GreasedRegion t = new GreasedRegion(width, height); 2460 double yPos, xPos, 2461 th, thx, thy, lon, lat, ipi = 0.99999 / Math.PI, 2462 rx = width * 0.25, irx = 1.0 / rx, hw = width * 0.5, 2463 ry = height * 0.5, iry = 1.0 / ry; 2464 2465 yPos = -ry; 2466 for (int y = 0; y < height; y++, yPos++) { 2467 thx = NumberTools.asin((yPos) * iry); 2468 lon = (thx == Math.PI * 0.5 || thx == Math.PI * -0.5) ? thx : Math.PI * irx * 0.5 / NumberTools.cos(thx); 2469 thy = thx * 2.0; 2470 lat = NumberTools.asin((thy + NumberTools.sin(thy)) * ipi); 2471 xPos = 0; 2472 for (int x = 0; x < width; x++, xPos++) { 2473 th = lon * (xPos - hw); 2474 if (th >= -3.141592653589793 && th <= 3.141592653589793 2475 && rectangular.contains((int) ((th + 1) * hw), (int) ((lat + 1) * ry))) { 2476 t.insert(x, y); 2477 } 2478 } 2479 } 2480 return t; 2481 } 2482 2483 @Override 2484 public int wrapX(final int x, int y) { 2485 y = Math.max(0, Math.min(y, height - 1)); 2486 if(x < edges[y << 1]) 2487 return edges[y << 1 | 1]; 2488 else if(x > edges[y << 1 | 1]) 2489 return edges[y << 1]; 2490 else return x; 2491 } 2492 2493 @Override 2494 public int wrapY(final int x, final int y) { 2495 return Math.max(0, Math.min(y, height - 1)); 2496 } 2497 2498 protected void regenerate(int startX, int startY, int usedWidth, int usedHeight, 2499 double landMod, double heatMod, int stateA, int stateB) 2500 { 2501 boolean fresh = false; 2502 if(cacheA != stateA || cacheB != stateB || landMod != landModifier || heatMod != heatModifier) 2503 { 2504 minHeight = Double.POSITIVE_INFINITY; 2505 maxHeight = Double.NEGATIVE_INFINITY; 2506 minHeat0 = Double.POSITIVE_INFINITY; 2507 maxHeat0 = Double.NEGATIVE_INFINITY; 2508 minHeat1 = Double.POSITIVE_INFINITY; 2509 maxHeat1 = Double.NEGATIVE_INFINITY; 2510 minHeat = Double.POSITIVE_INFINITY; 2511 maxHeat = Double.NEGATIVE_INFINITY; 2512 minWet0 = Double.POSITIVE_INFINITY; 2513 maxWet0 = Double.NEGATIVE_INFINITY; 2514 minWet = Double.POSITIVE_INFINITY; 2515 maxWet = Double.NEGATIVE_INFINITY; 2516 cacheA = stateA; 2517 cacheB = stateB; 2518 fresh = true; 2519 } 2520 rng.setState(stateA, stateB); 2521 long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong(); 2522 int t; 2523 2524 landModifier = (landMod <= 0) ? rng.nextDouble(0.29) + 0.91 : landMod; 2525 heatModifier = (heatMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : heatMod; 2526 2527 earth.remake(earthOriginal); 2528 2529 if(zoom > 0) 2530 { 2531 int stx = Math.min(Math.max((zoomStartX - (width >> 1)) / ((2 << zoom) - 2), 0), width ), 2532 sty = Math.min(Math.max((zoomStartY - (height >> 1)) / ((2 << zoom) - 2), 0), height); 2533 for (int z = 0; z < zoom; z++) { 2534 earth.zoom(stx, sty).expand8way().fray(0.5).expand(); 2535 } 2536 coast.remake(earth).not().fringe(2 << zoom).expand().fray(0.5); 2537 shallow.remake(earth).fringe(2 << zoom).expand().fray(0.5); 2538 } 2539 else 2540 { 2541 coast.remake(earth).not().fringe(2); 2542 shallow.remake(earth).fringe(2); 2543 } 2544 double p, 2545 ps, pc, 2546 qs, qc, 2547 h, temp, yPos, xPos, 2548 i_uw = usedWidth / (double)width, 2549 i_uh = usedHeight / (double)height, 2550 th, thx, thy, lon, lat, ipi = 0.99999 / Math.PI, 2551 rx = width * 0.25, irx = 1.0 / rx, hw = width * 0.5, 2552 ry = height * 0.5, iry = 1.0 / ry; 2553 yPos = startY - ry; 2554 for (int y = 0; y < height; y++, yPos += i_uh) { 2555 2556 thx = NumberTools.asin((yPos) * iry); 2557 lon = (thx == Math.PI * 0.5 || thx == Math.PI * -0.5) ? thx : Math.PI * irx * 0.5 / NumberTools.cos(thx); 2558 thy = thx * 2.0; 2559 lat = NumberTools.asin((thy + NumberTools.sin(thy)) * ipi); 2560 2561 qc = NumberTools.cos(lat); 2562 qs = NumberTools.sin(lat); 2563 2564 boolean inSpace = true; 2565 xPos = startX; 2566 for (int x = 0/*, xt = 0*/; x < width; x++, xPos += i_uw) { 2567 th = lon * (xPos - hw); 2568 if(th < -3.141592653589793 || th > 3.141592653589793) { 2569 heightCodeData[x][y] = 10000; 2570 inSpace = true; 2571 continue; 2572 } 2573 if(inSpace) 2574 { 2575 inSpace = false; 2576 edges[y << 1] = x; 2577 } 2578 edges[y << 1 | 1] = x; 2579 ps = NumberTools.sin(th) * qc; 2580 pc = NumberTools.cos(th) * qc; 2581 xPositions[x][y] = pc; 2582 yPositions[x][y] = ps; 2583 zPositions[x][y] = qs; 2584 if(earth.contains(x, y)) 2585 { 2586 h = NumberTools.swayTight(terrainLayered.getNoiseWithSeed(pc + terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5, 2587 ps, qs, seedA)) * 0.85; 2588 if(coast.contains(x, y)) 2589 h += 0.05; 2590 else 2591 h += 0.15; 2592 } 2593 else 2594 { 2595 h = NumberTools.swayTight(terrainLayered.getNoiseWithSeed(pc + terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5, 2596 ps, qs, seedA)) * -0.9; 2597 if(shallow.contains(x, y)) 2598 h = (h - 0.08) * 0.375; 2599 else 2600 h = (h - 0.125) * 0.75; 2601 } 2602 heightData[x][y] = h; 2603 heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps 2604 + otherRidged.getNoiseWithSeed(pc, ps, qs,seedB + seedC) 2605 , qs, seedB)); 2606 moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs 2607 + otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA) 2608 , seedC)); 2609 minHeightActual = Math.min(minHeightActual, h); 2610 maxHeightActual = Math.max(maxHeightActual, h); 2611 if(fresh) { 2612 minHeight = Math.min(minHeight, h); 2613 maxHeight = Math.max(maxHeight, h); 2614 2615 minHeat0 = Math.min(minHeat0, p); 2616 maxHeat0 = Math.max(maxHeat0, p); 2617 2618 minWet0 = Math.min(minWet0, temp); 2619 maxWet0 = Math.max(maxWet0, temp); 2620 } 2621 } 2622 minHeightActual = Math.min(minHeightActual, minHeight); 2623 maxHeightActual = Math.max(maxHeightActual, maxHeight); 2624 2625 } 2626 double heightDiff = 2.0 / (maxHeightActual - minHeightActual), 2627 heatDiff = 0.8 / (maxHeat0 - minHeat0), 2628 wetDiff = 1.0 / (maxWet0 - minWet0), 2629 hMod, 2630 halfHeight = (height - 1) * 0.5, i_half = 1.0 / (halfHeight); 2631 double minHeightActual0 = minHeightActual; 2632 double maxHeightActual0 = maxHeightActual; 2633 yPos = startY + i_uh; 2634 ps = Double.POSITIVE_INFINITY; 2635 pc = Double.NEGATIVE_INFINITY; 2636 2637 for (int y = 0; y < height; y++, yPos += i_uh) { 2638 temp = Math.pow(Math.abs(yPos - halfHeight) * i_half, 1.5); 2639 temp *= (2.4 - temp); 2640 temp = 2.2 - temp; 2641 for (int x = 0; x < width; x++) { 2642// heightData[x][y] = (h = (heightData[x][y] - minHeightActual) * heightDiff - 1.0); 2643// minHeightActual0 = Math.min(minHeightActual0, h); 2644// maxHeightActual0 = Math.max(maxHeightActual0, h); 2645 h = heightData[x][y]; 2646 if(heightCodeData[x][y] == 10000) { 2647 heightCodeData[x][y] = 1000; 2648 continue; 2649 } 2650 else 2651 heightCodeData[x][y] = (t = codeHeight(h)); 2652 hMod = 1.0; 2653 switch (t) { 2654 case 0: 2655 case 1: 2656 case 2: 2657 case 3: 2658 h = 0.4; 2659 hMod = 0.2; 2660 break; 2661 case 6: 2662 h = -0.1 * (h - forestLower - 0.08); 2663 break; 2664 case 7: 2665 h *= -0.25; 2666 break; 2667 case 8: 2668 h *= -0.4; 2669 break; 2670 default: 2671 h *= 0.05; 2672 } 2673 heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp); 2674 if (fresh) { 2675 ps = Math.min(ps, h); //minHeat0 2676 pc = Math.max(pc, h); //maxHeat0 2677 } 2678 } 2679 } 2680 if(fresh) 2681 { 2682 minHeat1 = ps; 2683 maxHeat1 = pc; 2684 } 2685 heatDiff = heatModifier / (maxHeat1 - minHeat1); 2686 qs = Double.POSITIVE_INFINITY; 2687 qc = Double.NEGATIVE_INFINITY; 2688 ps = Double.POSITIVE_INFINITY; 2689 pc = Double.NEGATIVE_INFINITY; 2690 2691 2692 for (int y = 0; y < height; y++) { 2693 for (int x = 0; x < width; x++) { 2694 heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff)); 2695 moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff); 2696 if (fresh) { 2697 qs = Math.min(qs, h); 2698 qc = Math.max(qc, h); 2699 ps = Math.min(ps, temp); 2700 pc = Math.max(pc, temp); 2701 } 2702 } 2703 } 2704 if(fresh) 2705 { 2706 minHeat = qs; 2707 maxHeat = qc; 2708 minWet = ps; 2709 maxWet = pc; 2710 } 2711 landData.refill(heightCodeData, 4, 999); 2712 } 2713 2714 } 2715 /** 2716 * A concrete implementation of {@link WorldMapGenerator} that imitates an infinite-distance perspective view of a 2717 * world, showing only one hemisphere, that should be as wide as it is tall (its outline is a circle). This uses an 2718 * <a href="https://en.wikipedia.org/wiki/Orthographic_projection_in_cartography">Orthographic projection</a> with 2719 * the latitude always at the equator. 2720 * <a href="http://squidpony.github.io/SquidLib/SpaceViewMap.png" >Example map, showing circular shape as if viewed 2721 * from afar</a> 2722 */ 2723 public static class SpaceViewMap extends WorldMapGenerator { 2724 // protected static final double terrainFreq = 1.65, terrainRidgedFreq = 1.8, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375, riverRidgedFreq = 21.7; 2725 protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375; 2726 protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY, 2727 minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY, 2728 minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY; 2729 2730 public final Noise3D terrain, heat, moisture, otherRidged, terrainLayered; 2731 public final double[][] xPositions, 2732 yPositions, 2733 zPositions; 2734 protected final int[] edges; 2735 2736 /** 2737 * Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space, 2738 * showing only one hemisphere at a time. 2739 * Always makes a 100x100 map. 2740 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 2741 * If you were using {@link SpaceViewMap#SpaceViewMap(long, int, int, Noise3D, double)}, then this would be the 2742 * same as passing the parameters {@code 0x1337BABE1337D00DL, 100, 100, DEFAULT_NOISE, 1.0}. 2743 */ 2744 public SpaceViewMap() { 2745 this(0x1337BABE1337D00DL, 100, 100, DEFAULT_NOISE, 1.0); 2746 } 2747 2748 /** 2749 * Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space, 2750 * showing only one hemisphere at a time. 2751 * Takes only the width/height of the map. The initial seed is set to the same large long 2752 * every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and 2753 * height of the map cannot be changed after the fact, but you can zoom in. 2754 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 2755 * 2756 * @param mapWidth the width of the map(s) to generate; cannot be changed later 2757 * @param mapHeight the height of the map(s) to generate; cannot be changed later 2758 */ 2759 public SpaceViewMap(int mapWidth, int mapHeight) { 2760 this(0x1337BABE1337D00DL, mapWidth, mapHeight, DEFAULT_NOISE,1.0); 2761 } 2762 2763 /** 2764 * Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space, 2765 * showing only one hemisphere at a time. 2766 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 2767 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 2768 * The width and height of the map cannot be changed after the fact, but you can zoom in. 2769 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 2770 * 2771 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 2772 * @param mapWidth the width of the map(s) to generate; cannot be changed later 2773 * @param mapHeight the height of the map(s) to generate; cannot be changed later 2774 */ 2775 public SpaceViewMap(long initialSeed, int mapWidth, int mapHeight) { 2776 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, 1.0); 2777 } 2778 2779 /** 2780 * Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space, 2781 * showing only one hemisphere at a time. 2782 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 2783 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 2784 * The width and height of the map cannot be changed after the fact, but you can zoom in. 2785 * Uses FastNoise as its noise generator, with the given octave multiplier affecting detail. 2786 * 2787 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 2788 * @param mapWidth the width of the map(s) to generate; cannot be changed later 2789 * @param mapHeight the height of the map(s) to generate; cannot be changed later 2790 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 2791 */ 2792 public SpaceViewMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) { 2793 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, octaveMultiplier); 2794 } 2795 2796 /** 2797 * Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space, 2798 * showing only one hemisphere at a time. 2799 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 2800 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 2801 * The width and height of the map cannot be changed after the fact, but you can zoom in. 2802 * Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. 2803 * 2804 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 2805 * @param mapWidth the width of the map(s) to generate; cannot be changed later 2806 * @param mapHeight the height of the map(s) to generate; cannot be changed later 2807 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 2808 */ 2809 public SpaceViewMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) { 2810 this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0); 2811 } 2812 2813 /** 2814 * Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space, 2815 * showing only one hemisphere at a time. 2816 * Takes an initial seed, the width/height of the map, and parameters for noise 2817 * generation (a {@link Noise3D} implementation, which is usually {@link FastNoise#instance}, and a 2818 * multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers 2819 * producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used, 2820 * since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map 2821 * cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for 2822 * {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the 2823 * seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance} 2824 * because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should 2825 * probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on 2826 * generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps 2827 * that don't require zooming. 2828 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 2829 * @param mapWidth the width of the map(s) to generate; cannot be changed later 2830 * @param mapHeight the height of the map(s) to generate; cannot be changed later 2831 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 2832 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 2833 */ 2834 public SpaceViewMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier) { 2835 super(initialSeed, mapWidth, mapHeight); 2836 xPositions = new double[width][height]; 2837 yPositions = new double[width][height]; 2838 zPositions = new double[width][height]; 2839 edges = new int[height << 1]; 2840 terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq); 2841 terrainLayered = new Noise.InverseLayered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325, 0.475); 2842// terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 8), terrainFreq); 2843// terrainLayered = new Noise.Layered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 5.25, 0.475); 2844 heat = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75); 2845 moisture = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55); 2846 otherRidged = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq); 2847 } 2848 2849 /** 2850 * Copies the SpaceViewMap {@code other} to construct a new one that is exactly the same. References will only 2851 * be shared to Noise classes. 2852 * @param other a SpaceViewMap to copy 2853 */ 2854 public SpaceViewMap(SpaceViewMap other) 2855 { 2856 super(other); 2857 terrain = other.terrain; 2858 terrainLayered = other.terrainLayered; 2859 heat = other.heat; 2860 moisture = other.moisture; 2861 otherRidged = other.otherRidged; 2862 minHeat0 = other.minHeat0; 2863 maxHeat0 = other.maxHeat0; 2864 minHeat1 = other.minHeat1; 2865 maxHeat1 = other.maxHeat1; 2866 minWet0 = other.minWet0; 2867 maxWet0 = other.maxWet0; 2868 xPositions = ArrayTools.copy(other.xPositions); 2869 yPositions = ArrayTools.copy(other.yPositions); 2870 zPositions = ArrayTools.copy(other.zPositions); 2871 edges = Arrays.copyOf(other.edges, other.edges.length); 2872 } 2873 2874 @Override 2875 public int wrapX(int x, int y) { 2876 y = Math.max(0, Math.min(y, height - 1)); 2877 return Math.max(edges[y << 1], Math.min(x, edges[y << 1 | 1])); 2878 } 2879 2880 @Override 2881 public int wrapY(final int x, final int y) { 2882 return Math.max(0, Math.min(y, height - 1)); 2883 } 2884 2885 //private static final double root2 = Math.sqrt(2.0), inverseRoot2 = 1.0 / root2, halfInverseRoot2 = 0.5 / root2; 2886 2887 protected void regenerate(int startX, int startY, int usedWidth, int usedHeight, 2888 double landMod, double heatMod, int stateA, int stateB) 2889 { 2890 boolean fresh = false; 2891 if(cacheA != stateA || cacheB != stateB || landMod != landModifier || heatMod != heatModifier) 2892 { 2893 minHeight = Double.POSITIVE_INFINITY; 2894 maxHeight = Double.NEGATIVE_INFINITY; 2895 minHeightActual = Double.POSITIVE_INFINITY; 2896 maxHeightActual = Double.NEGATIVE_INFINITY; 2897 minHeat0 = Double.POSITIVE_INFINITY; 2898 maxHeat0 = Double.NEGATIVE_INFINITY; 2899 minHeat1 = Double.POSITIVE_INFINITY; 2900 maxHeat1 = Double.NEGATIVE_INFINITY; 2901 minHeat = Double.POSITIVE_INFINITY; 2902 maxHeat = Double.NEGATIVE_INFINITY; 2903 minWet0 = Double.POSITIVE_INFINITY; 2904 maxWet0 = Double.NEGATIVE_INFINITY; 2905 minWet = Double.POSITIVE_INFINITY; 2906 maxWet = Double.NEGATIVE_INFINITY; 2907 cacheA = stateA; 2908 cacheB = stateB; 2909 fresh = true; 2910 } 2911 rng.setState(stateA, stateB); 2912 long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong(); 2913 int t; 2914 2915 landModifier = (landMod <= 0) ? rng.nextDouble(0.2) + 0.91 : landMod; 2916 heatModifier = (heatMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : heatMod; 2917 2918 double p, 2919 ps, pc, 2920 qs, qc, 2921 h, temp, yPos, xPos, iyPos, ixPos, 2922 i_uw = usedWidth / (double)width, 2923 i_uh = usedHeight / (double)height, 2924 th, lon, lat, rho, 2925 rx = width * 0.5, irx = i_uw / rx, 2926 ry = height * 0.5, iry = i_uh / ry; 2927 2928 yPos = startY - ry; 2929 iyPos = yPos / ry; 2930 for (int y = 0; y < height; y++, yPos += i_uh, iyPos += iry) { 2931 2932 boolean inSpace = true; 2933 xPos = startX - rx; 2934 ixPos = xPos / rx; 2935 for (int x = 0; x < width; x++, xPos += i_uw, ixPos += irx) { 2936 rho = Math.sqrt(ixPos * ixPos + iyPos * iyPos); 2937 if(rho > 1.0) { 2938 heightCodeData[x][y] = 10000; 2939 inSpace = true; 2940 continue; 2941 } 2942 if(inSpace) 2943 { 2944 inSpace = false; 2945 edges[y << 1] = x; 2946 } 2947 edges[y << 1 | 1] = x; 2948 th = NumberTools.asin(rho); // c 2949 lat = NumberTools.asin(iyPos); 2950 lon = centerLongitude + NumberTools.atan2(ixPos * rho, rho * NumberTools.cos(th)); 2951 2952 qc = NumberTools.cos(lat); 2953 qs = NumberTools.sin(lat); 2954 2955 pc = NumberTools.cos(lon) * qc; 2956 ps = NumberTools.sin(lon) * qc; 2957 2958 xPositions[x][y] = pc; 2959 yPositions[x][y] = ps; 2960 zPositions[x][y] = qs; 2961 heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(pc + 2962 terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5, 2963 ps, qs, seedA) + landModifier - 1.0); 2964// heightData[x][y] = (h = terrain4D.getNoiseWithSeed(pc, ps, qs, 2965// (terrainLayered.getNoiseWithSeed(pc, ps, qs, seedB - seedA) 2966// + terrain.getNoiseWithSeed(pc, ps, qs, seedC - seedB)) * 0.5, 2967// seedA) * landModifier); 2968 heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps 2969 + otherRidged.getNoiseWithSeed(pc, ps, qs,seedB + seedC) 2970 , qs, seedB)); 2971 moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs 2972 + otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA) 2973 , seedC)); 2974 minHeightActual = Math.min(minHeightActual, h); 2975 maxHeightActual = Math.max(maxHeightActual, h); 2976 if(fresh) { 2977 minHeight = Math.min(minHeight, h); 2978 maxHeight = Math.max(maxHeight, h); 2979 2980 minHeat0 = Math.min(minHeat0, p); 2981 maxHeat0 = Math.max(maxHeat0, p); 2982 2983 minWet0 = Math.min(minWet0, temp); 2984 maxWet0 = Math.max(maxWet0, temp); 2985 } 2986 } 2987 minHeightActual = Math.min(minHeightActual, minHeight); 2988 maxHeightActual = Math.max(maxHeightActual, maxHeight); 2989 2990 } 2991 double heatDiff = 0.8 / (maxHeat0 - minHeat0), 2992 wetDiff = 1.0 / (maxWet0 - minWet0), 2993 hMod, 2994 halfHeight = (height - 1) * 0.5, i_half = 1.0 / halfHeight; 2995 yPos = startY + i_uh; 2996 ps = Double.POSITIVE_INFINITY; 2997 pc = Double.NEGATIVE_INFINITY; 2998 2999 for (int y = 0; y < height; y++, yPos += i_uh) { 3000 temp = Math.abs(yPos - halfHeight) * i_half; 3001 temp *= (2.4 - temp); 3002 temp = 2.2 - temp; 3003 for (int x = 0; x < width; x++) { 3004 h = heightData[x][y]; 3005 if(heightCodeData[x][y] == 10000) { 3006 heightCodeData[x][y] = 1000; 3007 continue; 3008 } 3009 else 3010 heightCodeData[x][y] = (t = codeHeight(h)); 3011 hMod = 1.0; 3012 switch (t) { 3013 case 0: 3014 case 1: 3015 case 2: 3016 case 3: 3017 h = 0.4; 3018 hMod = 0.2; 3019 break; 3020 case 6: 3021 h = -0.1 * (h - forestLower - 0.08); 3022 break; 3023 case 7: 3024 h *= -0.25; 3025 break; 3026 case 8: 3027 h *= -0.4; 3028 break; 3029 default: 3030 h *= 0.05; 3031 } 3032 heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp); 3033 if (fresh) { 3034 ps = Math.min(ps, h); //minHeat0 3035 pc = Math.max(pc, h); //maxHeat0 3036 } 3037 } 3038 } 3039 if(fresh) 3040 { 3041 minHeat1 = ps; 3042 maxHeat1 = pc; 3043 } 3044 heatDiff = heatModifier / (maxHeat1 - minHeat1); 3045 qs = Double.POSITIVE_INFINITY; 3046 qc = Double.NEGATIVE_INFINITY; 3047 ps = Double.POSITIVE_INFINITY; 3048 pc = Double.NEGATIVE_INFINITY; 3049 3050 3051 for (int y = 0; y < height; y++) { 3052 for (int x = 0; x < width; x++) { 3053 heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff)); 3054 moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff); 3055 if (fresh) { 3056 qs = Math.min(qs, h); 3057 qc = Math.max(qc, h); 3058 ps = Math.min(ps, temp); 3059 pc = Math.max(pc, temp); 3060 } 3061 } 3062 } 3063 if(fresh) 3064 { 3065 minHeat = qs; 3066 maxHeat = qc; 3067 minWet = ps; 3068 maxWet = pc; 3069 } 3070 landData.refill(heightCodeData, 4, 999); 3071 } 3072 } 3073 /** 3074 * A concrete implementation of {@link WorldMapGenerator} that projects the world map onto a shape with a flat top 3075 * and bottom but near-circular sides. This is an equal-area projection, like EllipticalMap, so effects that fill 3076 * areas on a map like {@link PoliticalMapper} will fill (almost) equally on any part of the map. This has less 3077 * distortion on the far left and far right edges of the map than EllipticalMap, but the flat top and bottom are 3078 * probably very distorted in a small area near the poles. 3079 * This uses the <a href="https://en.wikipedia.org/wiki/Eckert_IV_projection">Eckert IV projection</a>. 3080 * <a href="https://squidpony.github.io/SquidLib/RoundSideWorldMap.png">Example map</a> 3081 */ 3082 public static class RoundSideMap extends WorldMapGenerator { 3083 // protected static final double terrainFreq = 1.35, terrainRidgedFreq = 1.8, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375, riverRidgedFreq = 21.7; 3084 protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375; 3085 protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY, 3086 minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY, 3087 minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY; 3088 3089 public final Noise3D terrain, heat, moisture, otherRidged, terrainLayered; 3090 public final double[][] xPositions, 3091 yPositions, 3092 zPositions; 3093 protected final int[] edges; 3094 3095 3096 /** 3097 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3098 * ellipse without distortion of the sizes of features but with significant distortion of shape. 3099 * Always makes a 200x100 map. 3100 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 3101 * If you were using {@link RoundSideMap#RoundSideMap(long, int, int, Noise3D, double)}, then this would be the 3102 * same as passing the parameters {@code 0x1337BABE1337D00DL, 200, 100, DEFAULT_NOISE, 1.0}. 3103 */ 3104 public RoundSideMap() { 3105 this(0x1337BABE1337D00DL, 200, 100, DEFAULT_NOISE, 1.0); 3106 } 3107 3108 /** 3109 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3110 * ellipse without distortion of the sizes of features but with significant distortion of shape. 3111 * Takes only the width/height of the map. The initial seed is set to the same large long 3112 * every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and 3113 * height of the map cannot be changed after the fact, but you can zoom in. 3114 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 3115 * 3116 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3117 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3118 */ 3119 public RoundSideMap(int mapWidth, int mapHeight) { 3120 this(0x1337BABE1337D00DL, mapWidth, mapHeight, DEFAULT_NOISE,1.0); 3121 } 3122 3123 /** 3124 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3125 * ellipse without distortion of the sizes of features but with significant distortion of shape. 3126 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 3127 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 3128 * The width and height of the map cannot be changed after the fact, but you can zoom in. 3129 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 3130 * 3131 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 3132 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3133 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3134 */ 3135 public RoundSideMap(long initialSeed, int mapWidth, int mapHeight) { 3136 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, 1.0); 3137 } 3138 3139 /** 3140 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3141 * ellipse without distortion of the sizes of features but with significant distortion of shape. 3142 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 3143 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 3144 * The width and height of the map cannot be changed after the fact, but you can zoom in. 3145 * Uses FastNoise as its noise generator, with the given octave multiplier affecting detail. 3146 * 3147 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 3148 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3149 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3150 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 3151 */ 3152 public RoundSideMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) { 3153 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, octaveMultiplier); 3154 } 3155 3156 /** 3157 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3158 * ellipse without distortion of the sizes of features but with significant distortion of shape. 3159 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 3160 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 3161 * The width and height of the map cannot be changed after the fact, but you can zoom in. 3162 * Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. The suggested Noise3D 3163 * implementation to use is {@link FastNoise#instance} 3164 * 3165 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 3166 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3167 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3168 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 3169 */ 3170 public RoundSideMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) { 3171 this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0); 3172 } 3173 3174 /** 3175 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3176 * ellipse without distortion of the sizes of features but with significant distortion of shape. 3177 * Takes an initial seed, the width/height of the map, and parameters for noise generation (a 3178 * {@link Noise3D} implementation, where {@link FastNoise#instance} is suggested, and a 3179 * multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers 3180 * producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used, 3181 * since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map 3182 * cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for 3183 * {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the 3184 * seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance} 3185 * because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should 3186 * probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on 3187 * generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps 3188 * that don't require zooming. 3189 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 3190 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3191 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3192 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 3193 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 3194 */ 3195 public RoundSideMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier) { 3196 super(initialSeed, mapWidth, mapHeight); 3197 xPositions = new double[width][height]; 3198 yPositions = new double[width][height]; 3199 zPositions = new double[width][height]; 3200 edges = new int[height << 1]; 3201 terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq); 3202 terrainLayered = new Noise.InverseLayered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325); 3203// terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 8), terrainFreq); 3204// terrainLayered = new Noise.Layered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 5.25); 3205 heat = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75); 3206 moisture = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55); 3207 otherRidged = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq); 3208 } 3209 3210 /** 3211 * Copies the RoundSideMap {@code other} to construct a new one that is exactly the same. References will only 3212 * be shared to Noise classes. 3213 * @param other a RoundSideMap to copy 3214 */ 3215 public RoundSideMap(RoundSideMap other) 3216 { 3217 super(other); 3218 terrain = other.terrain; 3219 terrainLayered = other.terrainLayered; 3220 heat = other.heat; 3221 moisture = other.moisture; 3222 otherRidged = other.otherRidged; 3223 minHeat0 = other.minHeat0; 3224 maxHeat0 = other.maxHeat0; 3225 minHeat1 = other.minHeat1; 3226 maxHeat1 = other.maxHeat1; 3227 minWet0 = other.minWet0; 3228 maxWet0 = other.maxWet0; 3229 xPositions = ArrayTools.copy(other.xPositions); 3230 yPositions = ArrayTools.copy(other.yPositions); 3231 zPositions = ArrayTools.copy(other.zPositions); 3232 edges = Arrays.copyOf(other.edges, other.edges.length); 3233 } 3234 3235 @Override 3236 public int wrapX(final int x, int y) { 3237 y = Math.max(0, Math.min(y, height - 1)); 3238 if(x < edges[y << 1]) 3239 return edges[y << 1 | 1]; 3240 else if(x > edges[y << 1 | 1]) 3241 return edges[y << 1]; 3242 else return x; 3243 } 3244 3245 @Override 3246 public int wrapY(final int x, final int y) { 3247 return Math.max(0, Math.min(y, height - 1)); 3248 } 3249 3250 protected void regenerate(int startX, int startY, int usedWidth, int usedHeight, 3251 double landMod, double heatMod, int stateA, int stateB) 3252 { 3253 boolean fresh = false; 3254 if(cacheA != stateA || cacheB != stateB || landMod != landModifier || heatMod != heatModifier) 3255 { 3256 minHeight = Double.POSITIVE_INFINITY; 3257 maxHeight = Double.NEGATIVE_INFINITY; 3258 minHeightActual = Double.POSITIVE_INFINITY; 3259 maxHeightActual = Double.NEGATIVE_INFINITY; 3260 minHeat0 = Double.POSITIVE_INFINITY; 3261 maxHeat0 = Double.NEGATIVE_INFINITY; 3262 minHeat1 = Double.POSITIVE_INFINITY; 3263 maxHeat1 = Double.NEGATIVE_INFINITY; 3264 minHeat = Double.POSITIVE_INFINITY; 3265 maxHeat = Double.NEGATIVE_INFINITY; 3266 minWet0 = Double.POSITIVE_INFINITY; 3267 maxWet0 = Double.NEGATIVE_INFINITY; 3268 minWet = Double.POSITIVE_INFINITY; 3269 maxWet = Double.NEGATIVE_INFINITY; 3270 cacheA = stateA; 3271 cacheB = stateB; 3272 fresh = true; 3273 } 3274 rng.setState(stateA, stateB); 3275 long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong(); 3276 int t; 3277 3278 landModifier = (landMod <= 0) ? rng.nextDouble(0.2) + 0.91 : landMod; 3279 heatModifier = (heatMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : heatMod; 3280 3281 double p, 3282 ps, pc, 3283 qs, qc, 3284 h, temp, yPos, xPos, 3285 i_uw = usedWidth / (double)width, 3286 i_uh = usedHeight / (double)height, 3287 th, thb, thx, thy, lon, lat, 3288 rx = width * 0.25, irx = 1.326500428177002 / rx, hw = width * 0.5, 3289 ry = height * 0.5, iry = 1.0 / ry; 3290 3291 yPos = startY - ry; 3292 for (int y = 0; y < height; y++, yPos += i_uh) { 3293 thy = yPos * iry;//NumberTools.sin(thb); 3294 thb = NumberTools.asin(thy); 3295 thx = NumberTools.cos(thb); 3296 //1.3265004 0.7538633073600218 1.326500428177002 3297 lon = (thx == Math.PI * 0.5 || thx == Math.PI * -0.5) ? 0x1.0p100 : irx / (0.42223820031577125 * (1.0 + thx)); 3298 qs = (thb + (thx + 2.0) * thy) * 0.2800495767557787; 3299 lat = NumberTools.asin(qs); 3300 3301 qc = NumberTools.cos(lat); 3302 3303 boolean inSpace = true; 3304 xPos = startX - hw; 3305 for (int x = 0/*, xt = 0*/; x < width; x++, xPos += i_uw) { 3306 th = lon * xPos; 3307 if(th < -3.141592653589793 || th > 3.141592653589793) { 3308 heightCodeData[x][y] = 10000; 3309 inSpace = true; 3310 continue; 3311 } 3312 if(inSpace) 3313 { 3314 inSpace = false; 3315 edges[y << 1] = x; 3316 } 3317 edges[y << 1 | 1] = x; 3318 th += centerLongitude; 3319 ps = NumberTools.sin(th) * qc; 3320 pc = NumberTools.cos(th) * qc; 3321 xPositions[x][y] = pc; 3322 yPositions[x][y] = ps; 3323 zPositions[x][y] = qs; 3324 heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(pc + 3325 terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5, 3326 ps, qs, seedA) + landModifier - 1.0); 3327 heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps 3328 + otherRidged.getNoiseWithSeed(pc, ps, qs,seedB + seedC) 3329 , qs, seedB)); 3330 moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs 3331 + otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA) 3332 , seedC)); 3333 minHeightActual = Math.min(minHeightActual, h); 3334 maxHeightActual = Math.max(maxHeightActual, h); 3335 if(fresh) { 3336 minHeight = Math.min(minHeight, h); 3337 maxHeight = Math.max(maxHeight, h); 3338 3339 minHeat0 = Math.min(minHeat0, p); 3340 maxHeat0 = Math.max(maxHeat0, p); 3341 3342 minWet0 = Math.min(minWet0, temp); 3343 maxWet0 = Math.max(maxWet0, temp); 3344 } 3345 } 3346 minHeightActual = Math.min(minHeightActual, minHeight); 3347 maxHeightActual = Math.max(maxHeightActual, maxHeight); 3348 3349 } 3350 double heatDiff = 0.8 / (maxHeat0 - minHeat0), 3351 wetDiff = 1.0 / (maxWet0 - minWet0), 3352 hMod, 3353 halfHeight = (height - 1) * 0.5, i_half = 1.0 / halfHeight; 3354 yPos = startY + i_uh; 3355 ps = Double.POSITIVE_INFINITY; 3356 pc = Double.NEGATIVE_INFINITY; 3357 3358 for (int y = 0; y < height; y++, yPos += i_uh) { 3359 temp = Math.abs(yPos - halfHeight) * i_half; 3360 temp *= (2.4 - temp); 3361 temp = 2.2 - temp; 3362 for (int x = 0; x < width; x++) { 3363 h = heightData[x][y]; 3364 if(heightCodeData[x][y] == 10000) { 3365 heightCodeData[x][y] = 1000; 3366 continue; 3367 } 3368 else 3369 heightCodeData[x][y] = (t = codeHeight(h)); 3370 hMod = 1.0; 3371 switch (t) { 3372 case 0: 3373 case 1: 3374 case 2: 3375 case 3: 3376 h = 0.4; 3377 hMod = 0.2; 3378 break; 3379 case 6: 3380 h = -0.1 * (h - forestLower - 0.08); 3381 break; 3382 case 7: 3383 h *= -0.25; 3384 break; 3385 case 8: 3386 h *= -0.4; 3387 break; 3388 default: 3389 h *= 0.05; 3390 } 3391 heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp); 3392 if (fresh) { 3393 ps = Math.min(ps, h); //minHeat0 3394 pc = Math.max(pc, h); //maxHeat0 3395 } 3396 } 3397 } 3398 if(fresh) 3399 { 3400 minHeat1 = ps; 3401 maxHeat1 = pc; 3402 } 3403 heatDiff = heatModifier / (maxHeat1 - minHeat1); 3404 qs = Double.POSITIVE_INFINITY; 3405 qc = Double.NEGATIVE_INFINITY; 3406 ps = Double.POSITIVE_INFINITY; 3407 pc = Double.NEGATIVE_INFINITY; 3408 3409 3410 for (int y = 0; y < height; y++) { 3411 for (int x = 0; x < width; x++) { 3412 heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff)); 3413 moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff); 3414 if (fresh) { 3415 qs = Math.min(qs, h); 3416 qc = Math.max(qc, h); 3417 ps = Math.min(ps, temp); 3418 pc = Math.max(pc, temp); 3419 } 3420 } 3421 } 3422 if(fresh) 3423 { 3424 minHeat = qs; 3425 maxHeat = qc; 3426 minWet = ps; 3427 maxWet = pc; 3428 } 3429 landData.refill(heightCodeData, 4, 999); 3430 } 3431 } 3432 /** 3433 * A concrete implementation of {@link WorldMapGenerator} that projects the world map onto a shape that resembles a 3434 * mix part-way between an ellipse and a rectangle. This is an equal-area projection, like EllipticalMap, so effects that fill 3435 * areas on a map like {@link PoliticalMapper} will fill (almost) equally on any part of the map. This has less 3436 * distortion around all the edges than the other maps here, especially when comparing the North and South poles 3437 * with RoundSideMap. 3438 * This uses the <a href="https://en.wikipedia.org/wiki/Tobler_hyperelliptical_projection">Tobler hyperelliptical projection</a>. 3439 * <a href="">Example map</a> 3440 */ 3441 public static class HyperellipticalMap extends WorldMapGenerator { 3442 protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375; 3443 protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY, 3444 minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY, 3445 minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY; 3446 3447 public final Noise3D terrain, heat, moisture, otherRidged, terrainLayered; 3448 public final double[][] xPositions, 3449 yPositions, 3450 zPositions; 3451 protected final int[] edges; 3452 private final double alpha, kappa, epsilon; 3453 private final double[] Z; 3454 3455 3456 /** 3457 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3458 * ellipse without distortion of the sizes of features but with significant distortion of shape. 3459 * Always makes a 200x100 map. 3460 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 3461 * If you were using {@link HyperellipticalMap#HyperellipticalMap(long, int, int, Noise3D, double)}, then this would be the 3462 * same as passing the parameters {@code 0x1337BABE1337D00DL, 200, 100, DEFAULT_NOISE, 1.0}. 3463 * <a href="http://squidpony.github.io/SquidLib/HyperellipseWorld.png" >Example map, showing special shape</a> 3464 */ 3465 public HyperellipticalMap() { 3466 this(0x1337BABE1337D00DL, 200, 100, DEFAULT_NOISE, 1.0); 3467 } 3468 3469 /** 3470 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3471 * ellipse without distortion of the sizes of features but with significant distortion of shape. 3472 * Takes only the width/height of the map. The initial seed is set to the same large long 3473 * every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and 3474 * height of the map cannot be changed after the fact, but you can zoom in. 3475 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 3476 * 3477 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3478 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3479 */ 3480 public HyperellipticalMap(int mapWidth, int mapHeight) { 3481 this(0x1337BABE1337D00DL, mapWidth, mapHeight, DEFAULT_NOISE,1.0); 3482 } 3483 3484 /** 3485 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3486 * ellipse without distortion of the sizes of features but with significant distortion of shape. 3487 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 3488 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 3489 * The width and height of the map cannot be changed after the fact, but you can zoom in. 3490 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 3491 * 3492 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 3493 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3494 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3495 */ 3496 public HyperellipticalMap(long initialSeed, int mapWidth, int mapHeight) { 3497 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, 1.0); 3498 } 3499 3500 /** 3501 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3502 * ellipse without distortion of the sizes of features but with significant distortion of shape. 3503 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 3504 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 3505 * The width and height of the map cannot be changed after the fact, but you can zoom in. 3506 * Uses FastNoise as its noise generator, with the given octave multiplier affecting detail. 3507 * 3508 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 3509 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3510 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3511 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 3512 */ 3513 public HyperellipticalMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) { 3514 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, octaveMultiplier); 3515 } 3516 3517 /** 3518 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3519 * ellipse without distortion of the sizes of features but with significant distortion of shape. 3520 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 3521 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 3522 * The width and height of the map cannot be changed after the fact, but you can zoom in. 3523 * Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. The suggested Noise3D 3524 * implementation to use is {@link FastNoise#instance}. 3525 * 3526 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 3527 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3528 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3529 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 3530 */ 3531 public HyperellipticalMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) { 3532 this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0); 3533 } 3534 3535 /** 3536 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3537 * ellipse without distortion of the sizes of features but with significant distortion of shape. 3538 * Takes an initial seed, the width/height of the map, and parameters for noise generation (a 3539 * {@link Noise3D} implementation, where {@link FastNoise#instance} is suggested, and a 3540 * multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers 3541 * producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used, 3542 * since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map 3543 * cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for 3544 * {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the 3545 * seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance} 3546 * because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should 3547 * probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on 3548 * generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps 3549 * that don't require zooming. 3550 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 3551 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3552 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3553 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 3554 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 3555 */ 3556 public HyperellipticalMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier){ 3557 this(initialSeed, mapWidth, mapHeight, noiseGenerator, octaveMultiplier, 0.0625, 2.5); 3558 } 3559 3560 /** 3561 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3562 * ellipse without distortion of the sizes of features but with significant distortion of shape. 3563 * Takes an initial seed, the width/height of the map, and parameters for noise generation (a 3564 * {@link Noise3D} implementation, where {@link FastNoise#instance} is suggested, and a 3565 * multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers 3566 * producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used, 3567 * since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map 3568 * cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for 3569 * {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the 3570 * seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance} 3571 * because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should 3572 * probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on 3573 * generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps 3574 * that don't require zooming. 3575 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 3576 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3577 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3578 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 3579 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 3580 * @param alpha one of the Tobler parameters; 0.0625 is the default and this can range from 0.0 to 1.0 at least 3581 * @param kappa one of the Tobler parameters; 2.5 is the default but 2.0-5.0 range values are also often used 3582 */ 3583 public HyperellipticalMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, 3584 double octaveMultiplier, double alpha, double kappa){ 3585 super(initialSeed, mapWidth, mapHeight); 3586 xPositions = new double[width][height]; 3587 yPositions = new double[width][height]; 3588 zPositions = new double[width][height]; 3589 edges = new int[height << 1]; 3590 terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq); 3591 terrainLayered = new Noise.InverseLayered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325); 3592 heat = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75); 3593 moisture = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55); 3594 otherRidged = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq); 3595 this.alpha = alpha; 3596 this.kappa = kappa; 3597 this.Z = new double[height << 2]; 3598 this.epsilon = ProjectionTools.simpsonIntegrateHyperellipse(0.0, 1.0, 0.25 / height, kappa); 3599 ProjectionTools.simpsonODESolveHyperellipse(1, this.Z, 0.25 / height, alpha, kappa, epsilon); 3600 } 3601 /** 3602 * Copies the HyperellipticalMap {@code other} to construct a new one that is exactly the same. References will only 3603 * be shared to Noise classes. 3604 * @param other a HyperellipticalMap to copy 3605 */ 3606 public HyperellipticalMap(HyperellipticalMap other) 3607 { 3608 super(other); 3609 terrain = other.terrain; 3610 terrainLayered = other.terrainLayered; 3611 heat = other.heat; 3612 moisture = other.moisture; 3613 otherRidged = other.otherRidged; 3614 minHeat0 = other.minHeat0; 3615 maxHeat0 = other.maxHeat0; 3616 minHeat1 = other.minHeat1; 3617 maxHeat1 = other.maxHeat1; 3618 minWet0 = other.minWet0; 3619 maxWet0 = other.maxWet0; 3620 xPositions = ArrayTools.copy(other.xPositions); 3621 yPositions = ArrayTools.copy(other.yPositions); 3622 zPositions = ArrayTools.copy(other.zPositions); 3623 edges = Arrays.copyOf(other.edges, other.edges.length); 3624 alpha = other.alpha; 3625 kappa = other.kappa; 3626 epsilon = other.epsilon; 3627 Z = Arrays.copyOf(other.Z, other.Z.length); 3628 } 3629 3630 3631 @Override 3632 public int wrapX(final int x, int y) { 3633 y = Math.max(0, Math.min(y, height - 1)); 3634 if(x < edges[y << 1]) 3635 return edges[y << 1 | 1]; 3636 else if(x > edges[y << 1 | 1]) 3637 return edges[y << 1]; 3638 else return x; 3639 } 3640 3641 @Override 3642 public int wrapY(final int x, final int y) { 3643 return Math.max(0, Math.min(y, height - 1)); 3644 } 3645 3646 /** 3647 * Given a latitude and longitude in radians (the conventional way of describing points on a globe), this gets the 3648 * (x,y) Coord on the map projection this generator uses that corresponds to the given lat-lon coordinates. If this 3649 * generator does not represent a globe (if it is toroidal, for instance) or if there is no "good way" to calculate 3650 * the projection for a given lat-lon coordinate, this returns null. This implementation never returns null. 3651 * If this is a supported operation and the parameters are valid, this returns a Coord with x between 0 and 3652 * {@link #width}, and y between 0 and {@link #height}, both exclusive. Automatically wraps the Coord's values using 3653 * {@link #wrapX(int, int)} and {@link #wrapY(int, int)}. 3654 * 3655 * @param latitude the latitude, from {@code Math.PI * -0.5} to {@code Math.PI * 0.5} 3656 * @param longitude the longitude, from {@code 0.0} to {@code Math.PI * 2.0} 3657 * @return the point at the given latitude and longitude, as a Coord with x between 0 and {@link #width} and y between 0 and {@link #height}, or null if unsupported 3658 */ 3659 @Override 3660 public Coord project(double latitude, double longitude) { 3661 final double z0 = Math.abs(NumberTools.sin(latitude)); 3662 final int i = Arrays.binarySearch(Z, z0); 3663 final double y; 3664 if (i >= 0) 3665 y = i/(Z.length-1.); 3666 else if (-i-1 >= Z.length) 3667 y = Z[Z.length-1]; 3668 else 3669 y = ((z0-Z[-i-2])/(Z[-i-1]-Z[-i-2]) + (-i-2))/(Z.length-1.); 3670 final int xx = (int)(((longitude - getCenterLongitude() + 12.566370614359172) % 6.283185307179586) * Math.abs(alpha + (1-alpha)*Math.pow(1 - Math.pow(Math.abs(y),kappa), 1/kappa)) + 0.5); 3671 final int yy = (int)(y * Math.signum(latitude) * height * 0.5 + 0.5); 3672 return Coord.get(wrapX(xx, yy), wrapY(xx, yy)); 3673 } 3674 3675 protected void regenerate(int startX, int startY, int usedWidth, int usedHeight, 3676 double landMod, double heatMod, int stateA, int stateB) 3677 { 3678 boolean fresh = false; 3679 if(cacheA != stateA || cacheB != stateB || landMod != landModifier || heatMod != heatModifier) 3680 { 3681 minHeight = Double.POSITIVE_INFINITY; 3682 maxHeight = Double.NEGATIVE_INFINITY; 3683 minHeightActual = Double.POSITIVE_INFINITY; 3684 maxHeightActual = Double.NEGATIVE_INFINITY; 3685 minHeat0 = Double.POSITIVE_INFINITY; 3686 maxHeat0 = Double.NEGATIVE_INFINITY; 3687 minHeat1 = Double.POSITIVE_INFINITY; 3688 maxHeat1 = Double.NEGATIVE_INFINITY; 3689 minHeat = Double.POSITIVE_INFINITY; 3690 maxHeat = Double.NEGATIVE_INFINITY; 3691 minWet0 = Double.POSITIVE_INFINITY; 3692 maxWet0 = Double.NEGATIVE_INFINITY; 3693 minWet = Double.POSITIVE_INFINITY; 3694 maxWet = Double.NEGATIVE_INFINITY; 3695 cacheA = stateA; 3696 cacheB = stateB; 3697 fresh = true; 3698 } 3699 rng.setState(stateA, stateB); 3700 long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong(); 3701 int t; 3702 3703 landModifier = (landMod <= 0) ? rng.nextDouble(0.2) + 0.91 : landMod; 3704 heatModifier = (heatMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : heatMod; 3705 3706 double p, 3707 ps, pc, 3708 qs, qc, 3709 h, temp, yPos, xPos, 3710 i_uw = usedWidth / (double)width, 3711 i_uh = usedHeight / (double)height, 3712 th, lon, 3713 rx = width * 0.5, irx = Math.PI / rx, hw = width * 0.5, 3714 ry = height * 0.5, iry = 1.0 / ry; 3715 3716 yPos = startY - ry; 3717 for (int y = 0; y < height; y++, yPos += i_uh) { 3718// thy = yPos * iry;//NumberTools.sin(thb); 3719// thb = asin(thy); 3720// thx = NumberTools.cos(thb); 3721// //1.3265004 0.7538633073600218 1.326500428177002 3722// lon = (thx == Math.PI * 0.5 || thx == Math.PI * -0.5) ? 0x1.0p100 : irx / (0.42223820031577125 * (1.0 + thx)); 3723// qs = (thb + (thx + 2.0) * thy) * 0.2800495767557787; 3724// lat = asin(qs); 3725// 3726// qc = NumberTools.cos(lat); 3727 3728 lon = NumberTools.asin(Z[(int)(0.5 + Math.abs(yPos*iry)*(Z.length-1))])*Math.signum(yPos); 3729 qs = NumberTools.sin(lon); 3730 qc = NumberTools.cos(lon); 3731 3732 boolean inSpace = true; 3733 xPos = startX - hw; 3734 for (int x = 0/*, xt = 0*/; x < width; x++, xPos += i_uw) { 3735 //th = lon * xPos; 3736 th = xPos * irx / Math.abs(alpha + (1-alpha)*ProjectionTools.hyperellipse(yPos * iry, kappa)); 3737 if(th < -3.141592653589793 || th > 3.141592653589793) { 3738 //if(th < -2.0 || th > 2.0) { 3739 heightCodeData[x][y] = 10000; 3740 inSpace = true; 3741 continue; 3742 } 3743 if(inSpace) 3744 { 3745 inSpace = false; 3746 edges[y << 1] = x; 3747 } 3748 edges[y << 1 | 1] = x; 3749 th += centerLongitude; 3750 ps = NumberTools.sin(th) * qc; 3751 pc = NumberTools.cos(th) * qc; 3752 xPositions[x][y] = pc; 3753 yPositions[x][y] = ps; 3754 zPositions[x][y] = qs; 3755 heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(pc + 3756 terrain.getNoiseWithSeed(pc, ps, qs, seedB - seedA) * 0.5, 3757 ps, qs, seedA) + landModifier - 1.0); 3758 heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps 3759 + otherRidged.getNoiseWithSeed(pc, ps, qs, seedB + seedC) 3760 , qs, seedB)); 3761 moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs 3762 + otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA) 3763 , seedC)); 3764 minHeightActual = Math.min(minHeightActual, h); 3765 maxHeightActual = Math.max(maxHeightActual, h); 3766 if(fresh) { 3767 minHeight = Math.min(minHeight, h); 3768 maxHeight = Math.max(maxHeight, h); 3769 3770 minHeat0 = Math.min(minHeat0, p); 3771 maxHeat0 = Math.max(maxHeat0, p); 3772 3773 minWet0 = Math.min(minWet0, temp); 3774 maxWet0 = Math.max(maxWet0, temp); 3775 } 3776 } 3777 minHeightActual = Math.min(minHeightActual, minHeight); 3778 maxHeightActual = Math.max(maxHeightActual, maxHeight); 3779 3780 } 3781 double heatDiff = 0.8 / (maxHeat0 - minHeat0), 3782 wetDiff = 1.0 / (maxWet0 - minWet0), 3783 hMod, 3784 halfHeight = (height - 1) * 0.5, i_half = 1.0 / halfHeight; 3785 yPos = startY + i_uh; 3786 ps = Double.POSITIVE_INFINITY; 3787 pc = Double.NEGATIVE_INFINITY; 3788 3789 for (int y = 0; y < height; y++, yPos += i_uh) { 3790 temp = Math.abs(yPos - halfHeight) * i_half; 3791 temp *= (2.4 - temp); 3792 temp = 2.2 - temp; 3793 for (int x = 0; x < width; x++) { 3794 h = heightData[x][y]; 3795 if(heightCodeData[x][y] == 10000) { 3796 heightCodeData[x][y] = 1000; 3797 continue; 3798 } 3799 else 3800 heightCodeData[x][y] = (t = codeHeight(h)); 3801 hMod = 1.0; 3802 switch (t) { 3803 case 0: 3804 case 1: 3805 case 2: 3806 case 3: 3807 h = 0.4; 3808 hMod = 0.2; 3809 break; 3810 case 6: 3811 h = -0.1 * (h - forestLower - 0.08); 3812 break; 3813 case 7: 3814 h *= -0.25; 3815 break; 3816 case 8: 3817 h *= -0.4; 3818 break; 3819 default: 3820 h *= 0.05; 3821 } 3822 heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp); 3823 if (fresh) { 3824 ps = Math.min(ps, h); //minHeat0 3825 pc = Math.max(pc, h); //maxHeat0 3826 } 3827 } 3828 } 3829 if(fresh) 3830 { 3831 minHeat1 = ps; 3832 maxHeat1 = pc; 3833 } 3834 heatDiff = heatModifier / (maxHeat1 - minHeat1); 3835 qs = Double.POSITIVE_INFINITY; 3836 qc = Double.NEGATIVE_INFINITY; 3837 ps = Double.POSITIVE_INFINITY; 3838 pc = Double.NEGATIVE_INFINITY; 3839 3840 3841 for (int y = 0; y < height; y++) { 3842 for (int x = 0; x < width; x++) { 3843 heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff)); 3844 moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff); 3845 if (fresh) { 3846 qs = Math.min(qs, h); 3847 qc = Math.max(qc, h); 3848 ps = Math.min(ps, temp); 3849 pc = Math.max(pc, temp); 3850 } 3851 } 3852 } 3853 if(fresh) 3854 { 3855 minHeat = qs; 3856 maxHeat = qc; 3857 minWet = ps; 3858 maxWet = pc; 3859 } 3860 landData.refill(heightCodeData, 4, 999); 3861 } 3862 } 3863 3864 /** 3865 * A concrete implementation of {@link WorldMapGenerator} that projects the world map onto an ellipse that should be 3866 * twice as wide as it is tall (although you can stretch it by width and height that don't have that ratio). 3867 * This uses the <a href="https://en.wikipedia.org/wiki/Hammer_projection">Hammer projection</a>, so the latitude 3868 * lines are curved instead of flat. The Mollweide projection that {@link EllipticalMap} uses has flat lines, but 3869 * the two projection are otherwise very similar, and are both equal-area (Hammer tends to have less significant 3870 * distortion around the edges, but the curvature of the latitude lines can be hard to visualize). 3871 * <a href="https://i.imgur.com/nmN6lMK.gifv">Preview image link of a world rotating</a>. 3872 */ 3873 public static class EllipticalHammerMap extends WorldMapGenerator { 3874 // protected static final double terrainFreq = 1.35, terrainRidgedFreq = 1.8, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375, riverRidgedFreq = 21.7; 3875 protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375; 3876 protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY, 3877 minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY, 3878 minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY; 3879 3880 public final Noise3D terrain, heat, moisture, otherRidged, terrainLayered; 3881 public final double[][] xPositions, 3882 yPositions, 3883 zPositions; 3884 protected final int[] edges; 3885 3886 3887 /** 3888 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3889 * ellipse without distortion of the sizes of features but with significant distortion of shape. This is very 3890 * similar to {@link EllipticalMap}, but has curved latitude lines instead of flat ones (it also may see more 3891 * internal usage because some operations on this projection are much faster and simpler). 3892 * Always makes a 200x100 map. 3893 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 3894 * If you were using {@link EllipticalHammerMap#EllipticalHammerMap(long, int, int, Noise3D, double)}, then this would be the 3895 * same as passing the parameters {@code 0x1337BABE1337D00DL, 200, 100, DEFAULT_NOISE, 1.0}. 3896 */ 3897 public EllipticalHammerMap() { 3898 this(0x1337BABE1337D00DL, 200, 100, DEFAULT_NOISE, 1.0); 3899 } 3900 3901 /** 3902 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3903 * ellipse without distortion of the sizes of features but with significant distortion of shape. This is very 3904 * similar to {@link EllipticalMap}, but has curved latitude lines instead of flat ones (it also may see more 3905 * internal usage because some operations on this projection are much faster and simpler). 3906 * Takes only the width/height of the map. The initial seed is set to the same large long 3907 * every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and 3908 * height of the map cannot be changed after the fact, but you can zoom in. 3909 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 3910 * 3911 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3912 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3913 */ 3914 public EllipticalHammerMap(int mapWidth, int mapHeight) { 3915 this(0x1337BABE1337D00DL, mapWidth, mapHeight, DEFAULT_NOISE,1.0); 3916 } 3917 3918 /** 3919 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3920 * ellipse without distortion of the sizes of features but with significant distortion of shape. This is very 3921 * similar to {@link EllipticalMap}, but has curved latitude lines instead of flat ones (it also may see more 3922 * internal usage because some operations on this projection are much faster and simpler). 3923 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 3924 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 3925 * The width and height of the map cannot be changed after the fact, but you can zoom in. 3926 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 3927 * 3928 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 3929 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3930 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3931 */ 3932 public EllipticalHammerMap(long initialSeed, int mapWidth, int mapHeight) { 3933 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, 1.0); 3934 } 3935 3936 /** 3937 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3938 * ellipse without distortion of the sizes of features but with significant distortion of shape. This is very 3939 * similar to {@link EllipticalMap}, but has curved latitude lines instead of flat ones (it also may see more 3940 * internal usage because some operations on this projection are much faster and simpler). 3941 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 3942 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 3943 * The width and height of the map cannot be changed after the fact, but you can zoom in. 3944 * Uses FastNoise as its noise generator, with the given octave multiplier affecting detail. 3945 * 3946 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 3947 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3948 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3949 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 3950 */ 3951 public EllipticalHammerMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) { 3952 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, octaveMultiplier); 3953 } 3954 3955 /** 3956 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3957 * ellipse without distortion of the sizes of features but with significant distortion of shape. This is very 3958 * similar to {@link EllipticalMap}, but has curved latitude lines instead of flat ones (it also may see more 3959 * internal usage because some operations on this projection are much faster and simpler). 3960 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 3961 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 3962 * The width and height of the map cannot be changed after the fact, but you can zoom in. 3963 * Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. The suggested Noise3D 3964 * implementation to use is {@link FastNoise#instance}. 3965 * 3966 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 3967 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3968 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3969 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 3970 */ 3971 public EllipticalHammerMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) { 3972 this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0); 3973 } 3974 3975 /** 3976 * Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an 3977 * ellipse without distortion of the sizes of features but with significant distortion of shape. This is very 3978 * similar to {@link EllipticalMap}, but has curved latitude lines instead of flat ones (it also may see more 3979 * internal usage because some operations on this projection are much faster and simpler). 3980 * Takes an initial seed, the width/height of the map, and parameters for noise generation (a 3981 * {@link Noise3D} implementation, where {@link FastNoise#instance} is suggested, and a 3982 * multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers 3983 * producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used, 3984 * since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map 3985 * cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for 3986 * {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the 3987 * seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance} 3988 * because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should 3989 * probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on 3990 * generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps 3991 * that don't require zooming. 3992 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 3993 * @param mapWidth the width of the map(s) to generate; cannot be changed later 3994 * @param mapHeight the height of the map(s) to generate; cannot be changed later 3995 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 3996 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 3997 */ 3998 public EllipticalHammerMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier) { 3999 super(initialSeed, mapWidth, mapHeight); 4000 xPositions = new double[width][height]; 4001 yPositions = new double[width][height]; 4002 zPositions = new double[width][height]; 4003 edges = new int[height << 1]; 4004 terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq); 4005 terrainLayered = new Noise.InverseLayered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325); 4006// terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 8), terrainFreq); 4007// terrainLayered = new Noise.Layered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 5.25); 4008 heat = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75); 4009 moisture = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55); 4010 otherRidged = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq); 4011 } 4012 4013 /** 4014 * Copies the EllipticalHammerMap {@code other} to construct a new one that is exactly the same. References will only 4015 * be shared to Noise classes. 4016 * @param other an EllipticalHammerMap to copy 4017 */ 4018 public EllipticalHammerMap(EllipticalHammerMap other) 4019 { 4020 super(other); 4021 terrain = other.terrain; 4022 terrainLayered = other.terrainLayered; 4023 heat = other.heat; 4024 moisture = other.moisture; 4025 otherRidged = other.otherRidged; 4026 minHeat0 = other.minHeat0; 4027 maxHeat0 = other.maxHeat0; 4028 minHeat1 = other.minHeat1; 4029 maxHeat1 = other.maxHeat1; 4030 minWet0 = other.minWet0; 4031 maxWet0 = other.maxWet0; 4032 xPositions = ArrayTools.copy(other.xPositions); 4033 yPositions = ArrayTools.copy(other.yPositions); 4034 zPositions = ArrayTools.copy(other.zPositions); 4035 edges = Arrays.copyOf(other.edges, other.edges.length); 4036 } 4037 4038 @Override 4039 public int wrapX(final int x, int y) { 4040 y = Math.max(0, Math.min(y, height - 1)); 4041 if(x < edges[y << 1]) 4042 return edges[y << 1 | 1]; 4043 else if(x > edges[y << 1 | 1]) 4044 return edges[y << 1]; 4045 else return x; 4046 } 4047 4048 @Override 4049 public int wrapY(final int x, final int y) { 4050 return Math.max(0, Math.min(y, height - 1)); 4051 } 4052 4053 protected void regenerate(int startX, int startY, int usedWidth, int usedHeight, 4054 double landMod, double heatMod, int stateA, int stateB) 4055 { 4056 boolean fresh = false; 4057 if(cacheA != stateA || cacheB != stateB || landMod != landModifier || heatMod != heatModifier) 4058 { 4059 minHeight = Double.POSITIVE_INFINITY; 4060 maxHeight = Double.NEGATIVE_INFINITY; 4061 minHeightActual = Double.POSITIVE_INFINITY; 4062 maxHeightActual = Double.NEGATIVE_INFINITY; 4063 minHeat0 = Double.POSITIVE_INFINITY; 4064 maxHeat0 = Double.NEGATIVE_INFINITY; 4065 minHeat1 = Double.POSITIVE_INFINITY; 4066 maxHeat1 = Double.NEGATIVE_INFINITY; 4067 minHeat = Double.POSITIVE_INFINITY; 4068 maxHeat = Double.NEGATIVE_INFINITY; 4069 minWet0 = Double.POSITIVE_INFINITY; 4070 maxWet0 = Double.NEGATIVE_INFINITY; 4071 minWet = Double.POSITIVE_INFINITY; 4072 maxWet = Double.NEGATIVE_INFINITY; 4073 cacheA = stateA; 4074 cacheB = stateB; 4075 fresh = true; 4076 } 4077 rng.setState(stateA, stateB); 4078 long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong(); 4079 int t; 4080 4081 landModifier = (landMod <= 0) ? rng.nextDouble(0.2) + 0.91 : landMod; 4082 heatModifier = (heatMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : heatMod; 4083 4084 double p, 4085 ps, pc, 4086 qs, qc, 4087 h, temp, yPos, xPos, 4088 z, th, lon, lat, 4089 rx = width * 0.5, hw = width * 0.5, root2 = Math.sqrt(2.0), 4090 irx = 1.0 / rx, iry = 2.0 / (double) height, 4091 xAdj, yAdj, 4092 i_uw = usedWidth / (double)(width), 4093 i_uh = usedHeight / (double)(height); 4094 4095 yPos = (startY - height * 0.5); 4096 for (int y = 0; y < height; y++, yPos += i_uh) { 4097 boolean inSpace = true; 4098 yAdj = yPos * iry; 4099 xPos = (startX - hw); 4100 for (int x = 0; x < width; x++, xPos += i_uw) { 4101 xAdj = xPos * irx; 4102 z = Math.sqrt(1.0 - 0.5 * xAdj * xAdj - 0.5 * yAdj * yAdj); 4103 th = z * yAdj * root2; 4104 lon = 2.0 * NumberTools.atan2((2.0 * z * z - 1.0), (z * xAdj * root2)); 4105 if(th != th || lon < 0.0) { 4106 heightCodeData[x][y] = 10000; 4107 inSpace = true; 4108 continue; 4109 } 4110 lat = NumberTools.asin(th); 4111 qc = NumberTools.cos(lat); 4112 qs = th; 4113 th = Math.PI - lon + centerLongitude; 4114 if(inSpace) 4115 { 4116 inSpace = false; 4117 edges[y << 1] = x; 4118 } 4119 edges[y << 1 | 1] = x; 4120 ps = NumberTools.sin(th) * qc; 4121 pc = NumberTools.cos(th) * qc; 4122 xPositions[x][y] = pc; 4123 yPositions[x][y] = ps; 4124 zPositions[x][y] = qs; 4125 heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(pc + 4126 terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5, 4127 ps, qs, seedA) + landModifier - 1.0); 4128 heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps 4129 + otherRidged.getNoiseWithSeed(pc, ps, qs,seedB + seedC) 4130 , qs, seedB)); 4131 moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs 4132 + otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA) 4133 , seedC)); 4134 minHeightActual = Math.min(minHeightActual, h); 4135 maxHeightActual = Math.max(maxHeightActual, h); 4136 if(fresh) { 4137 minHeight = Math.min(minHeight, h); 4138 maxHeight = Math.max(maxHeight, h); 4139 4140 minHeat0 = Math.min(minHeat0, p); 4141 maxHeat0 = Math.max(maxHeat0, p); 4142 4143 minWet0 = Math.min(minWet0, temp); 4144 maxWet0 = Math.max(maxWet0, temp); 4145 } 4146 } 4147 minHeightActual = Math.min(minHeightActual, minHeight); 4148 maxHeightActual = Math.max(maxHeightActual, maxHeight); 4149 4150 } 4151 double heatDiff = 0.8 / (maxHeat0 - minHeat0), 4152 wetDiff = 1.0 / (maxWet0 - minWet0), 4153 hMod, 4154 halfHeight = (height - 1) * 0.5, i_half = 1.0 / halfHeight; 4155 yPos = startY + i_uh; 4156 ps = Double.POSITIVE_INFINITY; 4157 pc = Double.NEGATIVE_INFINITY; 4158 4159 for (int y = 0; y < height; y++, yPos += i_uh) { 4160 temp = Math.abs(yPos - halfHeight) * i_half; 4161 temp *= (2.4 - temp); 4162 temp = 2.2 - temp; 4163 for (int x = 0; x < width; x++) { 4164 h = heightData[x][y]; 4165 if(heightCodeData[x][y] == 10000) { 4166 heightCodeData[x][y] = 1000; 4167 continue; 4168 } 4169 else 4170 heightCodeData[x][y] = (t = codeHeight(h)); 4171 hMod = 1.0; 4172 switch (t) { 4173 case 0: 4174 case 1: 4175 case 2: 4176 case 3: 4177 h = 0.4; 4178 hMod = 0.2; 4179 break; 4180 case 6: 4181 h = -0.1 * (h - forestLower - 0.08); 4182 break; 4183 case 7: 4184 h *= -0.25; 4185 break; 4186 case 8: 4187 h *= -0.4; 4188 break; 4189 default: 4190 h *= 0.05; 4191 } 4192 heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp); 4193 if (fresh) { 4194 ps = Math.min(ps, h); //minHeat0 4195 pc = Math.max(pc, h); //maxHeat0 4196 } 4197 } 4198 } 4199 if(fresh) 4200 { 4201 minHeat1 = ps; 4202 maxHeat1 = pc; 4203 } 4204 heatDiff = heatModifier / (maxHeat1 - minHeat1); 4205 qs = Double.POSITIVE_INFINITY; 4206 qc = Double.NEGATIVE_INFINITY; 4207 ps = Double.POSITIVE_INFINITY; 4208 pc = Double.NEGATIVE_INFINITY; 4209 4210 4211 for (int y = 0; y < height; y++) { 4212 for (int x = 0; x < width; x++) { 4213 heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff)); 4214 moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff); 4215 if (fresh) { 4216 qs = Math.min(qs, h); 4217 qc = Math.max(qc, h); 4218 ps = Math.min(ps, temp); 4219 pc = Math.max(pc, temp); 4220 } 4221 } 4222 } 4223 if(fresh) 4224 { 4225 minHeat = qs; 4226 maxHeat = qc; 4227 minWet = ps; 4228 maxWet = pc; 4229 } 4230 landData.refill(heightCodeData, 4, 999); 4231 } 4232 } 4233 4234 4235 4236 /** 4237 * A concrete implementation of {@link WorldMapGenerator} that imitates an infinite-distance perspective view of a 4238 * world, showing only one hemisphere, that should be as wide as it is tall (its outline is a circle). It should 4239 * look as a world would when viewed from space, and implements rotation differently to allow the planet to be 4240 * rotated without recalculating all the data, though it cannot zoom. Note that calling 4241 * {@link #setCenterLongitude(double)} does a lot more work than in other classes, but less than fully calling 4242 * {@link #generate()} in those classes, since it doesn't remake the map data at a slightly different rotation and 4243 * instead keeps a single map in use the whole time, using sections of it. This uses an 4244 * <a href="https://en.wikipedia.org/wiki/Orthographic_projection_in_cartography">Orthographic projection</a> with 4245 * the latitude always at the equator; the internal map is stored as a {@link SphereMap}, which uses a 4246 * <a href="https://en.wikipedia.org/wiki/Cylindrical_equal-area_projection#Discussion">cylindrical equal-area 4247 * projection</a>, specifically the Smyth equal-surface projection. 4248 * <br> 4249 * <a href="https://i.imgur.com/WNa5nQ1.gifv">Example view of a planet rotating</a>. 4250 * <a href="https://i.imgur.com/NV5IMd6.gifv">Another example</a>. 4251 */ 4252 public static class RotatingSpaceMap extends WorldMapGenerator { 4253 protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY, 4254 minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY, 4255 minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY; 4256 4257 public final double[][] xPositions, 4258 yPositions, 4259 zPositions; 4260 protected final int[] edges; 4261 public final SphereMap storedMap; 4262 /** 4263 * Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space, 4264 * showing only one hemisphere at a time. 4265 * Always makes a 100x100 map. 4266 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 4267 * If you were using {@link RotatingSpaceMap#RotatingSpaceMap(long, int, int, Noise3D, double)}, then this would be the 4268 * same as passing the parameters {@code 0x1337BABE1337D00DL, 100, 100, DEFAULT_NOISE, 1.0}. 4269 */ 4270 public RotatingSpaceMap() { 4271 this(0x1337BABE1337D00DL, 100, 100, DEFAULT_NOISE, 1.0); 4272 } 4273 4274 /** 4275 * Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space, 4276 * showing only one hemisphere at a time. 4277 * Takes only the width/height of the map. The initial seed is set to the same large long 4278 * every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and 4279 * height of the map cannot be changed after the fact, but you can zoom in. 4280 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 4281 * 4282 * @param mapWidth the width of the map(s) to generate; cannot be changed later 4283 * @param mapHeight the height of the map(s) to generate; cannot be changed later 4284 */ 4285 public RotatingSpaceMap(int mapWidth, int mapHeight) { 4286 this(0x1337BABE1337D00DL, mapWidth, mapHeight, DEFAULT_NOISE, 1.0); 4287 } 4288 4289 /** 4290 * Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space, 4291 * showing only one hemisphere at a time. 4292 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 4293 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 4294 * The width and height of the map cannot be changed after the fact, but you can zoom in. 4295 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 4296 * 4297 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 4298 * @param mapWidth the width of the map(s) to generate; cannot be changed later 4299 * @param mapHeight the height of the map(s) to generate; cannot be changed later 4300 */ 4301 public RotatingSpaceMap(long initialSeed, int mapWidth, int mapHeight) { 4302 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, 1.0); 4303 } 4304 4305 /** 4306 * Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space, 4307 * showing only one hemisphere at a time. 4308 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 4309 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 4310 * The width and height of the map cannot be changed after the fact, but you can zoom in. 4311 * Uses FastNoise as its noise generator, with the given octave multiplier affecting detail. 4312 * 4313 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 4314 * @param mapWidth the width of the map(s) to generate; cannot be changed later 4315 * @param mapHeight the height of the map(s) to generate; cannot be changed later 4316 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 4317 */ 4318 public RotatingSpaceMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) { 4319 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, octaveMultiplier); 4320 } 4321 4322 /** 4323 * Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space, 4324 * showing only one hemisphere at a time. 4325 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 4326 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 4327 * The width and height of the map cannot be changed after the fact, but you can zoom in. 4328 * Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. 4329 * 4330 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 4331 * @param mapWidth the width of the map(s) to generate; cannot be changed later 4332 * @param mapHeight the height of the map(s) to generate; cannot be changed later 4333 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 4334 */ 4335 public RotatingSpaceMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) { 4336 this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0); 4337 } 4338 4339 /** 4340 * Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space, 4341 * showing only one hemisphere at a time. 4342 * Takes an initial seed, the width/height of the map, and parameters for noise 4343 * generation (a {@link Noise3D} implementation, which is usually {@link FastNoise#instance}, and a 4344 * multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers 4345 * producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used, 4346 * since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map 4347 * cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for 4348 * {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the 4349 * seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance} 4350 * because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should 4351 * probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on 4352 * generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps 4353 * that don't require zooming. 4354 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 4355 * @param mapWidth the width of the map(s) to generate; cannot be changed later 4356 * @param mapHeight the height of the map(s) to generate; cannot be changed later 4357 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 4358 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 4359 */ 4360 public RotatingSpaceMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier) { 4361 super(initialSeed, mapWidth, mapHeight); 4362 xPositions = new double[mapWidth][mapHeight]; 4363 yPositions = new double[mapWidth][mapHeight]; 4364 zPositions = new double[mapWidth][mapHeight]; 4365 edges = new int[height << 1]; 4366 storedMap = new SphereMap(initialSeed, mapWidth << 1, mapHeight, noiseGenerator, octaveMultiplier); 4367 } 4368 4369 /** 4370 * Copies the RotatingSpaceMap {@code other} to construct a new one that is exactly the same. References will only 4371 * be shared to Noise classes. 4372 * @param other a RotatingSpaceMap to copy 4373 */ 4374 public RotatingSpaceMap(RotatingSpaceMap other) 4375 { 4376 super(other); 4377 minHeat0 = other.minHeat0; 4378 maxHeat0 = other.maxHeat0; 4379 minHeat1 = other.minHeat1; 4380 maxHeat1 = other.maxHeat1; 4381 minWet0 = other.minWet0; 4382 maxWet0 = other.maxWet0; 4383 xPositions = ArrayTools.copy(other.xPositions); 4384 yPositions = ArrayTools.copy(other.yPositions); 4385 zPositions = ArrayTools.copy(other.zPositions); 4386 edges = Arrays.copyOf(other.edges, other.edges.length); 4387 storedMap = new SphereMap(other.storedMap); 4388 } 4389 4390 4391 @Override 4392 public int wrapX(int x, int y) { 4393 y = Math.max(0, Math.min(y, height - 1)); 4394 return Math.max(edges[y << 1], Math.min(x, edges[y << 1 | 1])); 4395 } 4396 4397 @Override 4398 public int wrapY(final int x, final int y) { 4399 return Math.max(0, Math.min(y, height - 1)); 4400 } 4401 4402 @Override 4403 public void setCenterLongitude(double centerLongitude) { 4404 super.setCenterLongitude(centerLongitude); 4405 int ax, ay; 4406 double 4407 ps, pc, 4408 qs, qc, 4409 h, yPos, xPos, iyPos, ixPos, 4410 i_uw = usedWidth / (double)width, 4411 i_uh = usedHeight / (double)height, 4412 th, lon, lat, rho, 4413 i_pi = 1.0 / Math.PI, 4414 rx = width * 0.5, irx = i_uw / rx, 4415 ry = height * 0.5, iry = i_uh / ry; 4416 4417 yPos = startY - ry; 4418 iyPos = yPos / ry; 4419 for (int y = 0; y < height; y++, yPos += i_uh, iyPos += iry) { 4420 boolean inSpace = true; 4421 xPos = startX - rx; 4422 ixPos = xPos / rx; 4423 lat = NumberTools.asin(iyPos); 4424 for (int x = 0; x < width; x++, xPos += i_uw, ixPos += irx) { 4425 rho = (ixPos * ixPos + iyPos * iyPos); 4426 if(rho > 1.0) { 4427 heightCodeData[x][y] = 1000; 4428 inSpace = true; 4429 continue; 4430 } 4431 rho = Math.sqrt(rho); 4432 if(inSpace) 4433 { 4434 inSpace = false; 4435 edges[y << 1] = x; 4436 } 4437 edges[y << 1 | 1] = x; 4438 th = NumberTools.asin(rho); // c 4439 lon = removeExcess((centerLongitude + (NumberTools.atan2(ixPos * rho, rho * NumberTools.cos(th)))) * 0.5); 4440 4441 qs = lat * 0.6366197723675814; 4442 qc = qs + 1.0; 4443 int sf = (qs >= 0.0 ? (int) qs : (int) qs - 1) & -2; 4444 int cf = (qc >= 0.0 ? (int) qc : (int) qc - 1) & -2; 4445 qs -= sf; 4446 qc -= cf; 4447 qs *= 2.0 - qs; 4448 qc *= 2.0 - qc; 4449 qs = qs * (-0.775 - 0.225 * qs) * ((sf & 2) - 1); 4450 qc = qc * (-0.775 - 0.225 * qc) * ((cf & 2) - 1); 4451 4452 4453 ps = lon * 0.6366197723675814; 4454 pc = ps + 1.0; 4455 sf = (ps >= 0.0 ? (int) ps : (int) ps - 1) & -2; 4456 cf = (pc >= 0.0 ? (int) pc : (int) pc - 1) & -2; 4457 ps -= sf; 4458 pc -= cf; 4459 ps *= 2.0 - ps; 4460 pc *= 2.0 - pc; 4461 ps = ps * (-0.775 - 0.225 * ps) * ((sf & 2) - 1); 4462 pc = pc * (-0.775 - 0.225 * pc) * ((cf & 2) - 1); 4463 4464 ax = (int)((lon * i_pi + 1.0) * width); 4465 ay = (int)((qs + 1.0) * ry); 4466 4467// // Hammer projection, not an inverse projection like we usually use 4468// z = 1.0 / Math.sqrt(1 + qc * NumberTools.cos(lon * 0.5)); 4469// ax = (int)((qc * NumberTools.sin(lon * 0.5) * z + 1.0) * width); 4470// ay = (int)((qs * z + 1.0) * height * 0.5); 4471 4472 if(ax >= storedMap.width || ax < 0 || ay >= storedMap.height || ay < 0) 4473 { 4474 heightCodeData[x][y] = 1000; 4475 continue; 4476 } 4477 if(storedMap.heightCodeData[ax][ay] >= 1000) // for the seam we get when looping around 4478 { 4479 ay = storedMap.wrapY(ax, ay); 4480 ax = storedMap.wrapX(ax, ay); 4481 } 4482 4483 xPositions[x][y] = pc * qc; 4484 yPositions[x][y] = ps * qc; 4485 zPositions[x][y] = qs; 4486 4487 heightData[x][y] = h = storedMap.heightData[ax][ay]; 4488 heightCodeData[x][y] = codeHeight(h); 4489 heatData[x][y] = storedMap.heatData[ax][ay]; 4490 moistureData[x][y] = storedMap.moistureData[ax][ay]; 4491 4492 minHeightActual = Math.min(minHeightActual, h); 4493 maxHeightActual = Math.max(maxHeightActual, h); 4494 } 4495 minHeightActual = Math.min(minHeightActual, minHeight); 4496 maxHeightActual = Math.max(maxHeightActual, maxHeight); 4497 } 4498 4499 } 4500 4501 protected void regenerate(int startX, int startY, int usedWidth, int usedHeight, 4502 double landMod, double heatMod, int stateA, int stateB) 4503 { 4504 if(cacheA != stateA || cacheB != stateB)// || landMod != storedMap.landModifier || coolMod != storedMap.coolingModifier) 4505 { 4506 storedMap.regenerate(0, 0, width << 1, height, landMod, heatMod, stateA, stateB); 4507 minHeightActual = Double.POSITIVE_INFINITY; 4508 maxHeightActual = Double.NEGATIVE_INFINITY; 4509 4510 minHeight = storedMap.minHeight; 4511 maxHeight = storedMap.maxHeight; 4512 4513 minHeat0 = storedMap.minHeat0; 4514 maxHeat0 = storedMap.maxHeat0; 4515 4516 minHeat1 = storedMap.minHeat1; 4517 maxHeat1 = storedMap.maxHeat1; 4518 4519 minWet0 = storedMap.minWet0; 4520 maxWet0 = storedMap.maxWet0; 4521 4522 minHeat = storedMap.minHeat; 4523 maxHeat = storedMap.maxHeat; 4524 4525 minWet = storedMap.minWet; 4526 maxWet = storedMap.maxWet; 4527 4528 cacheA = stateA; 4529 cacheB = stateB; 4530 } 4531 setCenterLongitude(centerLongitude); 4532 landData.refill(heightCodeData, 4, 999); 4533 } 4534 } 4535 /** 4536 * A concrete implementation of {@link WorldMapGenerator} that does no projection of the map, as if the area were 4537 * completely flat or small enough that curvature is impossible to see. This also does not change heat levels at the 4538 * far north and south regions of the map, since it is meant for areas that are all about the same heat level. 4539 * <a href="http://squidpony.github.io/SquidLib/LocalMap.png" >Example map, showing lack of polar ice</a> 4540 */ 4541 public static class LocalMap extends WorldMapGenerator { 4542 protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375; 4543 //protected static final double terrainFreq = 1.65, terrainRidgedFreq = 1.8, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375, riverRidgedFreq = 21.7; 4544 protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY, 4545 minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY, 4546 minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY; 4547 4548 public final Noise.Ridged2D terrain, otherRidged; 4549 public final Noise.InverseLayered2D heat, moisture, terrainLayered; 4550 public final double[][] xPositions, 4551 yPositions, 4552 zPositions; 4553 4554 4555 /** 4556 * Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a 4557 * 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to 4558 * have significantly-exaggerated-in-size features while the equator is not distorted. 4559 * Always makes a 256x128 map. 4560 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 4561 * If you were using {@link LocalMap#LocalMap(long, int, int, Noise2D, double)}, then this would be the 4562 * same as passing the parameters {@code 0x1337BABE1337D00DL, 256, 128, DEFAULT_NOISE, 1.0}. 4563 */ 4564 public LocalMap() { 4565 this(0x1337BABE1337D00DL, 256, 128, DEFAULT_NOISE, 1.0); 4566 } 4567 4568 /** 4569 * Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a 4570 * 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to 4571 * have significantly-exaggerated-in-size features while the equator is not distorted. 4572 * Takes only the width/height of the map. The initial seed is set to the same large long 4573 * every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and 4574 * height of the map cannot be changed after the fact, but you can zoom in. 4575 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 4576 * 4577 * @param mapWidth the width of the map(s) to generate; cannot be changed later 4578 * @param mapHeight the height of the map(s) to generate; cannot be changed later 4579 */ 4580 public LocalMap(int mapWidth, int mapHeight) { 4581 this(0x1337BABE1337D00DL, mapWidth, mapHeight, DEFAULT_NOISE,1.0); 4582 } 4583 4584 /** 4585 * Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a 4586 * 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to 4587 * have significantly-exaggerated-in-size features while the equator is not distorted. 4588 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 4589 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 4590 * The width and height of the map cannot be changed after the fact, but you can zoom in. 4591 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 4592 * 4593 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 4594 * @param mapWidth the width of the map(s) to generate; cannot be changed later 4595 * @param mapHeight the height of the map(s) to generate; cannot be changed later 4596 */ 4597 public LocalMap(long initialSeed, int mapWidth, int mapHeight) { 4598 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, 1.0); 4599 } 4600 4601 /** 4602 * Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a 4603 * 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to 4604 * have significantly-exaggerated-in-size features while the equator is not distorted. 4605 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 4606 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 4607 * The width and height of the map cannot be changed after the fact, but you can zoom in. 4608 * Uses FastNoise as its noise generator, with the given octave multiplier affecting detail. 4609 * 4610 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 4611 * @param mapWidth the width of the map(s) to generate; cannot be changed later 4612 * @param mapHeight the height of the map(s) to generate; cannot be changed later 4613 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 4614 */ 4615 public LocalMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) { 4616 this(initialSeed, mapWidth, mapHeight, DEFAULT_NOISE, octaveMultiplier); 4617 } 4618 4619 /** 4620 * Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a 4621 * 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to 4622 * have significantly-exaggerated-in-size features while the equator is not distorted. 4623 * Takes an initial seed and the width/height of the map. The {@code initialSeed} 4624 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 4625 * The width and height of the map cannot be changed after the fact, but you can zoom in. 4626 * Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. 4627 * 4628 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 4629 * @param mapWidth the width of the map(s) to generate; cannot be changed later 4630 * @param mapHeight the height of the map(s) to generate; cannot be changed later 4631 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} 4632 */ 4633 public LocalMap(long initialSeed, int mapWidth, int mapHeight, Noise2D noiseGenerator) { 4634 this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0); 4635 } 4636 4637 /** 4638 * Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a 4639 * 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to 4640 * have significantly-exaggerated-in-size features while the equator is not distorted. 4641 * Takes an initial seed, the width/height of the map, and parameters for noise 4642 * generation (a {@link Noise3D} implementation, which is usually {@link FastNoise#instance}, and a 4643 * multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers 4644 * producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used, 4645 * since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map 4646 * cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for 4647 * {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the 4648 * seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance} 4649 * because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should 4650 * probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on 4651 * generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps 4652 * that don't require zooming. 4653 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 4654 * @param mapWidth the width of the map(s) to generate; cannot be changed later 4655 * @param mapHeight the height of the map(s) to generate; cannot be changed later 4656 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise#instance} 4657 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 4658 */ 4659 public LocalMap(long initialSeed, int mapWidth, int mapHeight, Noise2D noiseGenerator, double octaveMultiplier) { 4660 super(initialSeed, mapWidth, mapHeight); 4661 xPositions = new double[width][height]; 4662 yPositions = new double[width][height]; 4663 zPositions = new double[width][height]; 4664 4665 terrain = new Noise.Ridged2D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq); 4666 terrainLayered = new Noise.InverseLayered2D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325); 4667 heat = new Noise.InverseLayered2D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75); 4668 moisture = new Noise.InverseLayered2D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55); 4669 otherRidged = new Noise.Ridged2D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq); 4670 } 4671 4672 /** 4673 * Copies the LocalMap {@code other} to construct a new one that is exactly the same. References will only 4674 * be shared to Noise classes. 4675 * @param other a LocalMap to copy 4676 */ 4677 public LocalMap(LocalMap other) 4678 { 4679 super(other); 4680 terrain = other.terrain; 4681 terrainLayered = other.terrainLayered; 4682 heat = other.heat; 4683 moisture = other.moisture; 4684 otherRidged = other.otherRidged; 4685 minHeat0 = other.minHeat0; 4686 maxHeat0 = other.maxHeat0; 4687 minHeat1 = other.minHeat1; 4688 maxHeat1 = other.maxHeat1; 4689 minWet0 = other.minWet0; 4690 maxWet0 = other.maxWet0; 4691 xPositions = ArrayTools.copy(other.xPositions); 4692 yPositions = ArrayTools.copy(other.yPositions); 4693 zPositions = ArrayTools.copy(other.zPositions); 4694 } 4695 4696 @Override 4697 public int wrapX(final int x, final int y) { 4698 return Math.max(0, Math.min(x, width - 1)); 4699 } 4700 4701 @Override 4702 public int wrapY(final int x, final int y) { 4703 return Math.max(0, Math.min(y, height - 1)); 4704 } 4705 4706 protected void regenerate(int startX, int startY, int usedWidth, int usedHeight, 4707 double landMod, double heatMod, int stateA, int stateB) 4708 { 4709 boolean fresh = false; 4710 if(cacheA != stateA || cacheB != stateB || landMod != landModifier || heatMod != heatModifier) 4711 { 4712 minHeight = Double.POSITIVE_INFINITY; 4713 maxHeight = Double.NEGATIVE_INFINITY; 4714 minHeat0 = Double.POSITIVE_INFINITY; 4715 maxHeat0 = Double.NEGATIVE_INFINITY; 4716 minHeat1 = Double.POSITIVE_INFINITY; 4717 maxHeat1 = Double.NEGATIVE_INFINITY; 4718 minHeat = Double.POSITIVE_INFINITY; 4719 maxHeat = Double.NEGATIVE_INFINITY; 4720 minWet0 = Double.POSITIVE_INFINITY; 4721 maxWet0 = Double.NEGATIVE_INFINITY; 4722 minWet = Double.POSITIVE_INFINITY; 4723 maxWet = Double.NEGATIVE_INFINITY; 4724 cacheA = stateA; 4725 cacheB = stateB; 4726 fresh = true; 4727 } 4728 rng.setState(stateA, stateB); 4729 long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong(); 4730 int t; 4731 4732 landModifier = (landMod <= 0) ? rng.nextDouble(0.29) + 0.91 : landMod; 4733 heatModifier = (heatMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : heatMod; 4734 4735 double p, 4736 ps, pc, 4737 qs, qc, 4738 h, temp, 4739 i_w = 1.0 / width, i_h = 1.0 / (height), ii = Math.max(i_w, i_h), 4740 i_uw = usedWidth * i_w * ii, i_uh = usedHeight * i_h * ii, xPos, yPos = startY * i_h; 4741 for (int y = 0; y < height; y++, yPos += i_uh) { 4742 xPos = startX * i_w; 4743 for (int x = 0; x < width; x++, xPos += i_uw) { 4744 xPositions[x][y] = xPos; 4745 yPositions[x][y] = yPos; 4746 zPositions[x][y] = 0.0; 4747 heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(xPos + 4748 terrain.getNoiseWithSeed(xPos, yPos, seedB - seedA) * 0.5, 4749 yPos, seedA) + landModifier - 1.0); 4750 heatData[x][y] = (p = heat.getNoiseWithSeed(xPos, yPos 4751 + otherRidged.getNoiseWithSeed(xPos, yPos, seedB + seedC), 4752 seedB)); 4753 temp = otherRidged.getNoiseWithSeed(xPos, yPos, seedC + seedA); 4754 moistureData[x][y] = (temp = moisture.getNoiseWithSeed(xPos - temp, yPos + temp, seedC)); 4755 4756 minHeightActual = Math.min(minHeightActual, h); 4757 maxHeightActual = Math.max(maxHeightActual, h); 4758 if(fresh) { 4759 minHeight = Math.min(minHeight, h); 4760 maxHeight = Math.max(maxHeight, h); 4761 4762 minHeat0 = Math.min(minHeat0, p); 4763 maxHeat0 = Math.max(maxHeat0, p); 4764 4765 minWet0 = Math.min(minWet0, temp); 4766 maxWet0 = Math.max(maxWet0, temp); 4767 } 4768 } 4769 minHeightActual = Math.min(minHeightActual, minHeight); 4770 maxHeightActual = Math.max(maxHeightActual, maxHeight); 4771 4772 } 4773 double heatDiff = 0.8 / (maxHeat0 - minHeat0), 4774 wetDiff = 1.0 / (maxWet0 - minWet0), 4775 hMod; 4776 yPos = startY * i_h + i_uh; 4777 ps = Double.POSITIVE_INFINITY; 4778 pc = Double.NEGATIVE_INFINITY; 4779 4780 for (int y = 0; y < height; y++, yPos += i_uh) { 4781 for (int x = 0; x < width; x++) { 4782 h = heightData[x][y]; 4783 heightCodeData[x][y] = (t = codeHeight(h)); 4784 hMod = 1.0; 4785 switch (t) { 4786 case 0: 4787 case 1: 4788 case 2: 4789 case 3: 4790 h = 0.4; 4791 hMod = 0.2; 4792 break; 4793 case 6: 4794 h = -0.1 * (h - forestLower - 0.08); 4795 break; 4796 case 7: 4797 h *= -0.25; 4798 break; 4799 case 8: 4800 h *= -0.4; 4801 break; 4802 default: 4803 h *= 0.05; 4804 } 4805 heatData[x][y] = (h = ((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6); 4806 if (fresh) { 4807 ps = Math.min(ps, h); //minHeat0 4808 pc = Math.max(pc, h); //maxHeat0 4809 } 4810 } 4811 } 4812 if(fresh) 4813 { 4814 minHeat1 = ps; 4815 maxHeat1 = pc; 4816 } 4817 heatDiff = heatModifier / (maxHeat1 - minHeat1); 4818 qs = Double.POSITIVE_INFINITY; 4819 qc = Double.NEGATIVE_INFINITY; 4820 ps = Double.POSITIVE_INFINITY; 4821 pc = Double.NEGATIVE_INFINITY; 4822 4823 4824 for (int y = 0; y < height; y++) { 4825 for (int x = 0; x < width; x++) { 4826 heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff)); 4827 moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff); 4828 if (fresh) { 4829 qs = Math.min(qs, h); 4830 qc = Math.max(qc, h); 4831 ps = Math.min(ps, temp); 4832 pc = Math.max(pc, temp); 4833 } 4834 } 4835 } 4836 if(fresh) 4837 { 4838 minHeat = qs; 4839 maxHeat = qc; 4840 minWet = ps; 4841 maxWet = pc; 4842 } 4843 landData.refill(heightCodeData, 4, 999); 4844 } 4845 } 4846 4847 /** 4848 * An unusual map generator that imitates an existing local map (such as a map of Australia, which it can do by 4849 * default), without applying any projection or changing heat levels in the polar regions or equator. 4850 * <a href="http://squidpony.github.io/SquidLib/LocalMimicMap.png" >Example map, showing a variant on Australia</a> 4851 */ 4852 public static class LocalMimicMap extends LocalMap 4853 { 4854 public GreasedRegion earth; 4855 public GreasedRegion shallow; 4856 public GreasedRegion coast; 4857 public GreasedRegion earthOriginal; 4858 /** 4859 * Constructs a concrete WorldMapGenerator for a map that should look like Australia, without projecting the 4860 * land positions or changing heat by latitude. Always makes a 256x256 map. 4861 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 4862 * If you were using {@link LocalMimicMap#LocalMimicMap(long, Noise2D, double)}, then this would be the 4863 * same as passing the parameters {@code 0x1337BABE1337D00DL, DEFAULT_NOISE, 1.0}. 4864 */ 4865 public LocalMimicMap() { 4866 this(0x1337BABE1337D00DL 4867 , DEFAULT_NOISE, 1.0); 4868 } 4869 4870 /** 4871 * Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the 4872 * given GreasedRegion's "on" cells, without projecting the land positions or changing heat by latitude. 4873 * The initial seed is set to the same large long every time, and it's likely that you would set the seed when 4874 * you call {@link #generate(long)}. The width and height of the map cannot be changed after the fact. 4875 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 4876 * 4877 * @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied 4878 */ 4879 public LocalMimicMap(GreasedRegion toMimic) { 4880 this(0x1337BABE1337D00DL, toMimic, DEFAULT_NOISE,1.0); 4881 } 4882 4883 /** 4884 * Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the 4885 * given GreasedRegion's "on" cells, without projecting the land positions or changing heat by latitude. 4886 * Takes an initial seed and the GreasedRegion containing land positions. The {@code initialSeed} 4887 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 4888 * The width and height of the map cannot be changed after the fact. 4889 * Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail. 4890 * 4891 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 4892 * @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied 4893 */ 4894 public LocalMimicMap(long initialSeed, GreasedRegion toMimic) { 4895 this(initialSeed, toMimic, DEFAULT_NOISE, 1.0); 4896 } 4897 4898 /** 4899 * Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the 4900 * given GreasedRegion's "on" cells, without projecting the land positions or changing heat by latitude. 4901 * Takes an initial seed, the GreasedRegion containing land positions, and a multiplier that affects the level 4902 * of detail by increasing or decreasing the number of octaves of noise used. The {@code initialSeed} 4903 * parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}. 4904 * The width and height of the map cannot be changed after the fact. 4905 * Uses FastNoise as its noise generator, with the given octave multiplier affecting detail. 4906 * 4907 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 4908 * @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied 4909 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 4910 */ 4911 public LocalMimicMap(long initialSeed, GreasedRegion toMimic, double octaveMultiplier) { 4912 this(initialSeed, toMimic, DEFAULT_NOISE, octaveMultiplier); 4913 } 4914 4915 /** 4916 * Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the 4917 * given GreasedRegion's "on" cells, without projecting the land positions or changing heat by latitude. 4918 * Takes an initial seed, the GreasedRegion containing land positions, and parameters for noise generation (a 4919 * {@link Noise3D} implementation, which is usually {@link FastNoise#instance}. The {@code initialSeed} 4920 * parameter may or may not be used, since you can specify the seed to use when you call 4921 * {@link #generate(long)}. The width and height of the map cannot be changed after the fact. Both FastNoise 4922 * and FastNoise make sense to use for {@code noiseGenerator}, and the seed it's constructed with doesn't matter 4923 * because this will change the seed several times at different scales of noise (it's fine to use the static 4924 * {@link FastNoise#instance} or {@link FastNoise#instance} because they have no changing state between runs 4925 * of the program). Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. 4926 * 4927 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 4928 * @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied 4929 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} or {@link FastNoise} 4930 */ 4931 public LocalMimicMap(long initialSeed, GreasedRegion toMimic, Noise2D noiseGenerator) { 4932 this(initialSeed, toMimic, noiseGenerator, 1.0); 4933 } 4934 4935 /** 4936 * Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the 4937 * given GreasedRegion's "on" cells, using an elliptical projection (specifically, a Mollweide projection). 4938 * Takes an initial seed, the GreasedRegion containing land positions, parameters for noise generation (a 4939 * {@link Noise3D} implementation, which is usually {@link FastNoise#instance}, and a multiplier on how many 4940 * octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers producing even more 4941 * detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used, 4942 * since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map 4943 * cannot be changed after the fact. FastNoise will be the fastest 3D generator to use for 4944 * {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the 4945 * seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance} 4946 * because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should 4947 * probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on 4948 * generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps 4949 * that don't require zooming. 4950 * @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate 4951 * @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied 4952 * @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} or {@link FastNoise} 4953 * @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal 4954 */ 4955 public LocalMimicMap(long initialSeed, GreasedRegion toMimic, Noise2D noiseGenerator, double octaveMultiplier) { 4956 super(initialSeed, toMimic.width, toMimic.height, noiseGenerator, octaveMultiplier); 4957 earth = toMimic; 4958 earthOriginal = earth.copy(); 4959 coast = earth.copy().not().fringe(2); 4960 shallow = earth.copy().fringe(2); 4961 } 4962 4963 /** 4964 * Constructs a 256x256 unprojected local map that will use land forms with a similar shape to Australia. 4965 * @param initialSeed 4966 * @param noiseGenerator 4967 * @param octaveMultiplier 4968 */ 4969 public LocalMimicMap(long initialSeed, Noise2D noiseGenerator, double octaveMultiplier) 4970 { 4971 this(initialSeed, 4972 GreasedRegion.decompress(LZSPlus.decompress( 4973 "Ƥ䒅⒐᮰囨䈢ħ䐤࠰ࠨ•Ⱙအ䎢ŘňÆ䴣ȢؤF䭠゠ᔤ∠偰ഀ\u0560₠ኼܨā᭮笁\u242AЦᇅ扰रࠦ吠䠪ࠦ䠧娮⠬䠬❁Ềក\u1CAA͠敠ἒ慽Ê䄄洡儠䋻䨡㈠䙬坈མŨྈ䞻䛊哚晪⁞倰h·䡂Ļæ抂㴢္\u082E搧䈠ᇩᒠ᩠ɀ༨ʨڤʃ奲ࢠ፠ᆙả䝆䮳りĩ(ॠી᧰྄e॑ᤙ䒠剠⁌ဥࠩФΝ䂂⢴ᑠ㺀ᢣ䗨dBqÚ扜冢\u0FE5\u0A62䐠劣ေ¯䂍䞀ၰ\u0E67ᐓ〈ᄠ塠Ѡ̀ာ⠤ᡤŒęጓ憒‱〿䌳℔ᐼ䊢⁚䤿ӣ◚㙀\u0C74Ӹ抠⣀ĨNJǸ䁃း₺Ý䂁ᜤ䢑V⁄樫焠\u0A60\u2E78⎲Ĉ䁎勯戡璠悈ᠥ嘡⩩‰ನ檨㡕䶪၁@恑ࠣ䘣ࢠᅀᡎ劰桠Өॢಸ熛փࢸ䀹ఽ䅠勖ਰ۴̄ጺಢ䈠ᙠᨭ\u2FE0焠Ӡܼ䇂䒠ᯀԨĠ愜᪅䦥㶐ୀ\u09C5Ƣ*䂕ॹ∠咠р\u0604У無~⁆Г椠痠\u1CA9Ⱓס㩖ᝋ司楠२ญⳘ䬣汤ǿã㱩ᖷ掠Àݒ㑁c‾䮴,\u2452僢ᰣ缠ɋ乨\u0378䁡绑ס傓䁔瀾ሺÑ䀤ो刡开烀\u0A76Ё䈠䈰״Áj⁑䠡戢碠㘀አ䃉㪙嘈ʂø⸪௰₈㐲暤ƩDᬿ䂖剙書\u0FE0㴢\u0089㘩Ĉ䰵掀栰杁4〡Ƞ⭀\u1AE0㠰㹨Zコത\u009E䂖ࠠⴠ縣吠ᆠʡ㡀䀧否䣝Ӧ愠Ⓚ\u1CA2ಠո*①ӈԥ獀խ@㟬箬㐱\u31BE簽Ɛᩆᇞ稯禚⟶⣑аβǚ㥎Ḇ⌢㑆 搡⁗ဣ刣\u0C45䑒8怺₵⤦a5ਵ㏰ᩄ猢ฦ䬞㐷䈠呠カ愠ۀ\u1C92傠ᅼ߃ᙊ䢨ၠླྀš亀ƴ̰刷ʼ墨愠 " 4974 )), 4975 noiseGenerator, octaveMultiplier); 4976 } 4977 4978 /** 4979 * Copies the LocalMimicMap {@code other} to construct a new one that is exactly the same. References will only 4980 * be shared to Noise classes. 4981 * @param other a LocalMimicMap to copy 4982 */ 4983 public LocalMimicMap(LocalMimicMap other) 4984 { 4985 super(other); 4986 earth = other.earth.copy(); 4987 earthOriginal = other.earthOriginal.copy(); 4988 coast = other.coast.copy(); 4989 shallow = other.shallow.copy(); 4990 } 4991 4992 4993 4994 protected void regenerate(int startX, int startY, int usedWidth, int usedHeight, 4995 double landMod, double heatMod, int stateA, int stateB) 4996 { 4997 boolean fresh = false; 4998 if(cacheA != stateA || cacheB != stateB || landMod != landModifier || heatMod != heatModifier) 4999 { 5000 minHeight = Double.POSITIVE_INFINITY; 5001 maxHeight = Double.NEGATIVE_INFINITY; 5002 minHeat0 = Double.POSITIVE_INFINITY; 5003 maxHeat0 = Double.NEGATIVE_INFINITY; 5004 minHeat1 = Double.POSITIVE_INFINITY; 5005 maxHeat1 = Double.NEGATIVE_INFINITY; 5006 minHeat = Double.POSITIVE_INFINITY; 5007 maxHeat = Double.NEGATIVE_INFINITY; 5008 minWet0 = Double.POSITIVE_INFINITY; 5009 maxWet0 = Double.NEGATIVE_INFINITY; 5010 minWet = Double.POSITIVE_INFINITY; 5011 maxWet = Double.NEGATIVE_INFINITY; 5012 cacheA = stateA; 5013 cacheB = stateB; 5014 fresh = true; 5015 } 5016 rng.setState(stateA, stateB); 5017 long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong(); 5018 int t; 5019 5020 landModifier = (landMod <= 0) ? rng.nextDouble(0.29) + 0.91 : landMod; 5021 heatModifier = (heatMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : heatMod; 5022 5023 earth.remake(earthOriginal); 5024 5025 if(zoom > 0) 5026 { 5027 int stx = Math.min(Math.max((zoomStartX - (width >> 1)) / ((2 << zoom) - 2), 0), width ), 5028 sty = Math.min(Math.max((zoomStartY - (height >> 1)) / ((2 << zoom) - 2), 0), height); 5029 for (int z = 0; z < zoom; z++) { 5030 earth.zoom(stx, sty).expand8way().fray(0.5).expand(); 5031 } 5032 coast.remake(earth).not().fringe(2 << zoom).expand().fray(0.5); 5033 shallow.remake(earth).fringe(2 << zoom).expand().fray(0.5); 5034 } 5035 else 5036 { 5037 coast.remake(earth).not().fringe(2); 5038 shallow.remake(earth).fringe(2); 5039 } 5040 double p, 5041 ps, pc, 5042 qs, qc, 5043 h, temp, 5044 i_w = 1.0 / width, i_h = 1.0 / (height), 5045 i_uw = usedWidth * i_w * i_w, i_uh = usedHeight * i_h * i_h, xPos, yPos = startY * i_h; 5046 for (int y = 0; y < height; y++, yPos += i_uh) { 5047 xPos = startX * i_w; 5048 for (int x = 0, xt = 0; x < width; x++, xPos += i_uw) { 5049 xPositions[x][y] = (xPos - .5) * 2.0; 5050 yPositions[x][y] = (yPos - .5) * 2.0; 5051 zPositions[x][y] = 0.0; 5052 5053 if(earth.contains(x, y)) 5054 { 5055 h = NumberTools.swayTight(terrainLayered.getNoiseWithSeed(xPos + 5056 terrain.getNoiseWithSeed(xPos, yPos, seedB - seedA) * 0.5, 5057 yPos, seedA)) * 0.85; 5058 if(coast.contains(x, y)) 5059 h += 0.05; 5060 else 5061 h += 0.15; 5062 } 5063 else 5064 { 5065 h = NumberTools.swayTight(terrainLayered.getNoiseWithSeed(xPos + 5066 terrain.getNoiseWithSeed(xPos, yPos, seedB - seedA) * 0.5, 5067 yPos, seedA)) * -0.9; 5068 if(shallow.contains(x, y)) 5069 h = (h - 0.08) * 0.375; 5070 else 5071 h = (h - 0.125) * 0.75; 5072 } 5073 //h += landModifier - 1.0; 5074 heightData[x][y] = h; 5075 heatData[x][y] = (p = heat.getNoiseWithSeed(xPos, yPos 5076 + otherRidged.getNoiseWithSeed(xPos, yPos, seedB + seedC), 5077 seedB)); 5078 temp = otherRidged.getNoiseWithSeed(xPos, yPos, seedC + seedA); 5079 moistureData[x][y] = (temp = moisture.getNoiseWithSeed(xPos - temp, yPos + temp, seedC)); 5080 5081 minHeightActual = Math.min(minHeightActual, h); 5082 maxHeightActual = Math.max(maxHeightActual, h); 5083 if(fresh) { 5084 minHeight = Math.min(minHeight, h); 5085 maxHeight = Math.max(maxHeight, h); 5086 5087 minHeat0 = Math.min(minHeat0, p); 5088 maxHeat0 = Math.max(maxHeat0, p); 5089 5090 minWet0 = Math.min(minWet0, temp); 5091 maxWet0 = Math.max(maxWet0, temp); 5092 } 5093 } 5094 minHeightActual = Math.min(minHeightActual, minHeight); 5095 maxHeightActual = Math.max(maxHeightActual, maxHeight); 5096 5097 } 5098 double heatDiff = 0.8 / (maxHeat0 - minHeat0), 5099 wetDiff = 1.0 / (maxWet0 - minWet0), 5100 hMod; 5101 yPos = startY * i_h + i_uh; 5102 ps = Double.POSITIVE_INFINITY; 5103 pc = Double.NEGATIVE_INFINITY; 5104 5105 for (int y = 0; y < height; y++, yPos += i_uh) { 5106 for (int x = 0; x < width; x++) { 5107 h = heightData[x][y]; 5108 heightCodeData[x][y] = (t = codeHeight(h)); 5109 hMod = 1.0; 5110 switch (t) { 5111 case 0: 5112 case 1: 5113 case 2: 5114 case 3: 5115 h = 0.4; 5116 hMod = 0.2; 5117 break; 5118 case 6: 5119 h = -0.1 * (h - forestLower - 0.08); 5120 break; 5121 case 7: 5122 h *= -0.25; 5123 break; 5124 case 8: 5125 h *= -0.4; 5126 break; 5127 default: 5128 h *= 0.05; 5129 } 5130 heatData[x][y] = (h = ((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6); 5131 if (fresh) { 5132 ps = Math.min(ps, h); //minHeat0 5133 pc = Math.max(pc, h); //maxHeat0 5134 } 5135 } 5136 } 5137 if(fresh) 5138 { 5139 minHeat1 = ps; 5140 maxHeat1 = pc; 5141 } 5142 heatDiff = heatModifier / (maxHeat1 - minHeat1); 5143 qs = Double.POSITIVE_INFINITY; 5144 qc = Double.NEGATIVE_INFINITY; 5145 ps = Double.POSITIVE_INFINITY; 5146 pc = Double.NEGATIVE_INFINITY; 5147 5148 5149 for (int y = 0; y < height; y++) { 5150 for (int x = 0; x < width; x++) { 5151 heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff)); 5152 moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff); 5153 if (fresh) { 5154 qs = Math.min(qs, h); 5155 qc = Math.max(qc, h); 5156 ps = Math.min(ps, temp); 5157 pc = Math.max(pc, temp); 5158 } 5159 } 5160 } 5161 if(fresh) 5162 { 5163 minHeat = qs; 5164 maxHeat = qc; 5165 minWet = ps; 5166 maxWet = pc; 5167 } 5168 landData.refill(heightCodeData, 4, 999); 5169 } 5170 } 5171}