squidpony.squidgrid.mapping.WorldMapGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of squidlib-util Show documentation
Show all versions of squidlib-util Show documentation
SquidLib platform-independent logic and utility code. Please refer to
https://github.com/SquidPony/SquidLib .
package squidpony.squidgrid.mapping;
import squidpony.LZSPlus;
import squidpony.annotation.Beta;
import squidpony.squidmath.*;
import squidpony.squidmath.Noise.Noise2D;
import squidpony.squidmath.Noise.Noise3D;
import squidpony.squidmath.Noise.Noise4D;
import java.io.Serializable;
import java.util.Arrays;
/**
* Can be used to generate world maps with a wide variety of data, starting with height, temperature and moisture.
* From there, you can determine biome information in as much detail as your game needs, with default implementations
* available; one assigns a single biome to each cell based on heat/moisture, and the other gives a gradient between two
* biome types for every cell. The maps this produces with {@link SphereMap} are valid for spherical world projections,
* while the maps from {@link TilingMap} are for toroidal world projections and will wrap from edge to opposite edge
* seamlessly thanks to a technique from the
* Accidental Noise Library that involves getting a 2D slice of 4D Simplex noise. Because of how Simplex noise
* works, this also allows extremely high zoom levels for all types of map as long as certain parameters are within
* reason. Other world maps produce more conventional shapes, like {@link SpaceViewMap} and {@link RotatingSpaceMap}
* make a view of a marble-like world from space, and others make more unconventional shapes, like {@link EllipticalMap}
* or {@link EllipticalHammerMap}, which form a 2:1 ellipse shape that accurately keeps sizes but not relative shapes,
* {@link RoundSideMap}, which forms a pill-shape, and {@link HyperellipticalMap}, which takes parameters so it can fit
* any shape between a circle or ellipse and a rectangle (the default is a slightly squared-off ellipse). You can access
* the height map with the {@link #heightData} field, the heat map with the {@link #heatData} field, the moisture map
* with the {@link #moistureData} field, and a special map that stores ints representing the codes for various ranges of
* elevation (0 to 8 inclusive, with 0 the deepest ocean and 8 the highest mountains) with {@link #heightCodeData}. The
* last map should be noted as being the simplest way to find what is land and what is water; any height code 4 or
* greater is land, and any height code 3 or less is water.
*
* Biome mapping is likely to need customization per-game, but some good starting points are {@link SimpleBiomeMapper},
* which stores one biome per cell, and {@link DetailedBiomeMapper}, which gives each cell a midway value between two
* biomes.
*/
@Beta
public abstract class WorldMapGenerator implements Serializable {
private static final long serialVersionUID = 1L;
public final int width, height;
public int seedA, seedB, cacheA, cacheB;
public GWTRNG rng;
public final double[][] heightData, heatData, moistureData;
public final GreasedRegion landData;
public final int[][] heightCodeData;
public double landModifier = -1.0, coolingModifier = 1.0,
minHeight = Double.POSITIVE_INFINITY, maxHeight = Double.NEGATIVE_INFINITY,
minHeightActual = Double.POSITIVE_INFINITY, maxHeightActual = Double.NEGATIVE_INFINITY,
minHeat = Double.POSITIVE_INFINITY, maxHeat = Double.NEGATIVE_INFINITY,
minWet = Double.POSITIVE_INFINITY, maxWet = Double.NEGATIVE_INFINITY;
protected double centerLongitude = 0.0;
/**
* Gets the longitude line the map is centered on, which should usually be between 0 and 2 * PI.
* @return the longitude line the map is centered on, in radians from 0 to 2 * PI
*/
public double getCenterLongitude() {
return centerLongitude;
}
/**
* Sets the center longitude line to a longitude measured in radians, from 0 to 2 * PI. Positive arguments will be
* corrected with modulo, but negative ones may not always act as expected, and are strongly discouraged.
* @param centerLongitude the longitude to center the map projection on, from 0 to 2 * PI (can be any non-negative double).
*/
public void setCenterLongitude(double centerLongitude) {
this.centerLongitude = centerLongitude % 6.283185307179586;
}
public int zoom = 0, startX = 0, startY = 0, usedWidth, usedHeight;
protected IntVLA startCacheX = new IntVLA(8), startCacheY = new IntVLA(8);
protected int zoomStartX = 0, zoomStartY = 0;
public static final double
deepWaterLower = -1.0, deepWaterUpper = -0.7, // 0
mediumWaterLower = -0.7, mediumWaterUpper = -0.3, // 1
shallowWaterLower = -0.3, shallowWaterUpper = -0.1, // 2
coastalWaterLower = -0.1, coastalWaterUpper = 0.02, // 3
sandLower = 0.02, sandUpper = 0.12, // 4
grassLower = 0.14, grassUpper = 0.35, // 5
forestLower = 0.35, forestUpper = 0.6, // 6
rockLower = 0.6, rockUpper = 0.8, // 7
snowLower = 0.8, snowUpper = 1.0; // 8
protected static double removeExcess(double radians)
{
radians *= 0.6366197723675814;
final int floor = (radians >= 0.0 ? (int) radians : (int) radians - 1);
return (radians - (floor & -2) - ((floor & 1) << 1)) * (Math.PI);
// if(radians < -Math.PI || radians > Math.PI)
// System.out.println("UH OH, radians produced: " + radians);
// if(Math.random() < 0.00001)
// System.out.println(radians);
// return radians;
}
/**
* Constructs a WorldMapGenerator (this class is abstract, so you should typically call this from a subclass or as
* part of an anonymous class that implements {@link #regenerate(int, int, int, int, double, double, int, int)}).
* Always makes a 256x256 map. If you were using {@link WorldMapGenerator#WorldMapGenerator(long, int, int)}, then
* this would be the same as passing the parameters {@code 0x1337BABE1337D00DL, 256, 256}.
*/
protected WorldMapGenerator()
{
this(0x1337BABE1337D00DL, 256, 256);
}
/**
* Constructs a WorldMapGenerator (this class is abstract, so you should typically call this from a subclass or as
* part of an anonymous class that implements {@link #regenerate(int, int, int, int, double, double, int, int)}).
* Takes only the width/height of the map. The initial seed is set to the same large long
* every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and
* height of the map cannot be changed after the fact, but you can zoom in.
*
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
protected WorldMapGenerator(int mapWidth, int mapHeight)
{
this(0x1337BABE1337D00DL, mapWidth, mapHeight);
}
/**
* Constructs a WorldMapGenerator (this class is abstract, so you should typically call this from a subclass or as
* part of an anonymous class that implements {@link #regenerate(int, int, int, int, double, double, int, int)}).
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
protected WorldMapGenerator(long initialSeed, int mapWidth, int mapHeight)
{
width = mapWidth;
height = mapHeight;
usedWidth = width;
usedHeight = height;
seedA = (int) (initialSeed & 0xFFFFFFFFL);
seedB = (int) (initialSeed >>> 32);
cacheA = ~seedA;
cacheB = ~seedB;
rng = new GWTRNG(seedA, seedB);
heightData = new double[width][height];
heatData = new double[width][height];
moistureData = new double[width][height];
landData = new GreasedRegion(width, height);
// riverData = new GreasedRegion(width, height);
// lakeData = new GreasedRegion(width, height);
// partialRiverData = new GreasedRegion(width, height);
// partialLakeData = new GreasedRegion(width, height);
// workingData = new GreasedRegion(width, height);
heightCodeData = new int[width][height];
}
/**
* Generates a world using a random RNG state and all parameters randomized.
* The worlds this produces will always have width and height as specified in the constructor (default 256x256).
* You can call {@link #zoomIn(int, int, int)} to double the resolution and center on the specified area, but the width
* and height of the 2D arrays this changed, such as {@link #heightData} and {@link #moistureData} will be the same.
*/
public void generate()
{
generate(rng.nextLong());
}
/**
* Generates a world using the specified RNG state as a long. Other parameters will be randomized, using the same
* RNG state to start with.
* The worlds this produces will always have width and height as specified in the constructor (default 256x256).
* You can call {@link #zoomIn(int, int, int)} to double the resolution and center on the specified area, but the width
* and height of the 2D arrays this changed, such as {@link #heightData} and {@link #moistureData} will be the same.
* @param state the state to give this generator's RNG; if the same as the last call, this will reuse data
*/
public void generate(long state) {
generate(-1.0, -1.0, state);
}
/**
* Generates a world using the specified RNG state as a long, with specific water and cooling modifiers that affect
* the land-water ratio and the average temperature, respectively.
* The worlds this produces will always have width and height as specified in the constructor (default 256x256).
* You can call {@link #zoomIn(int, int, int)} to double the resolution and center on the specified area, but the width
* and height of the 2D arrays this changed, such as {@link #heightData} and {@link #moistureData} will be the same.
* @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
* @param coolMod 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
* @param state the state to give this generator's RNG; if the same as the last call, this will reuse data
*/
public void generate(double landMod, double coolMod, long state)
{
if(cacheA != (int) (state & 0xFFFFFFFFL) || cacheB != (int) (state >>> 32) ||
landMod != landModifier || coolMod != coolingModifier)
{
seedA = (int) (state & 0xFFFFFFFFL);
seedB = (int) (state >>> 32);
zoom = 0;
startCacheX.clear();
startCacheY.clear();
startCacheX.add(0);
startCacheY.add(0);
zoomStartX = width >> 1;
zoomStartY = height >> 1;
}
//System.out.printf("generate, zoomStartX: %d, zoomStartY: %d\n", zoomStartX, zoomStartY);
regenerate(startX = (zoomStartX >> zoom) - (width >> 1 + zoom), startY = (zoomStartY >> zoom) - (height >> 1 + zoom),
//startCacheX.peek(), startCacheY.peek(),
usedWidth = (width >> zoom), usedHeight = (height >> zoom), landMod, coolMod, seedA, seedB);
}
/**
* Halves the resolution of the map and doubles the area it covers; the 2D arrays this uses keep their sizes. This
* version of zoomOut always zooms out from the center of the currently used area.
*
* Only has an effect if you have previously zoomed in using {@link #zoomIn(int, int, int)} or its overload.
*/
public void zoomOut()
{
zoomOut(1, width >> 1, height >> 1);
}
/**
* Halves the resolution of the map and doubles the area it covers repeatedly, halving {@code zoomAmount} times; the
* 2D arrays this uses keep their sizes. This version of zoomOut allows you to specify where the zoom should be
* centered, using the current coordinates (if the map size is 256x256, then coordinates should be between 0 and
* 255, and will refer to the currently used area and not necessarily the full world size).
*
* Only has an effect if you have previously zoomed in using {@link #zoomIn(int, int, int)} or its overload.
* @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
* @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
*/
public void zoomOut(int zoomAmount, int zoomCenterX, int zoomCenterY)
{
zoomAmount = Math.min(zoom, zoomAmount);
if(zoomAmount == 0) return;
if(zoomAmount < 0) {
zoomIn(-zoomAmount, zoomCenterX, zoomCenterY);
return;
}
if(zoom > 0)
{
if(cacheA != seedA || cacheB != seedB)
{
generate(rng.nextLong());
}
zoomStartX = Math.min(Math.max(
(zoomStartX + (zoomCenterX - (width >> 1))) >> zoomAmount,
width >> 1), (width << zoom - zoomAmount) - (width >> 1));
zoomStartY = Math.min(Math.max(
(zoomStartY + (zoomCenterY - (height >> 1))) >> zoomAmount,
height >> 1), (height << zoom - zoomAmount) - (height >> 1));
// System.out.printf("zoomOut, zoomStartX: %d, zoomStartY: %d\n", zoomStartX, zoomStartY);
zoom -= zoomAmount;
startCacheX.pop();
startCacheY.pop();
startCacheX.add(Math.min(Math.max(startCacheX.pop() + (zoomCenterX >> zoom + 1) - (width >> zoom + 2),
0), width - (width >> zoom)));
startCacheY.add(Math.min(Math.max(startCacheY.pop() + (zoomCenterY >> zoom + 1) - (height >> zoom + 2),
0), height - (height >> zoom)));
// zoomStartX = Math.min(Math.max((zoomStartX >> 1) + (zoomCenterX >> zoom + 1) - (width >> zoom + 2),
// 0), width - (width >> zoom));
// zoomStartY = Math.min(Math.max((zoomStartY >> 1) + (zoomCenterY >> zoom + 1) - (height >> zoom + 2),
// 0), height - (height >> zoom));
regenerate(startX = (zoomStartX >> zoom) - (width >> zoom + 1), startY = (zoomStartY >> zoom) - (height >> zoom + 1),
//startCacheX.peek(), startCacheY.peek(),
usedWidth = width >> zoom, usedHeight = height >> zoom,
landModifier, coolingModifier, cacheA, cacheB);
rng.setState(cacheA, cacheB);
}
}
/**
* Doubles the resolution of the map and halves the area it covers; the 2D arrays this uses keep their sizes. This
* version of zoomIn always zooms in to the center of the currently used area.
*
* Although there is no technical restriction on maximum zoom, zooming in more than 5 times (64x scale or greater)
* will make the map appear somewhat less realistic due to rounded shapes appearing more bubble-like and less like a
* normal landscape.
*/
public void zoomIn()
{
zoomIn(1, width >> 1, height >> 1);
}
/**
* Doubles the resolution of the map and halves the area it covers repeatedly, doubling {@code zoomAmount} times;
* the 2D arrays this uses keep their sizes. This version of zoomIn allows you to specify where the zoom should be
* centered, using the current coordinates (if the map size is 256x256, then coordinates should be between 0 and
* 255, and will refer to the currently used area and not necessarily the full world size).
*
* Although there is no technical restriction on maximum zoom, zooming in more than 5 times (64x scale or greater)
* will make the map appear somewhat less realistic due to rounded shapes appearing more bubble-like and less like a
* normal landscape.
* @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
* @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
*/
public void zoomIn(int zoomAmount, int zoomCenterX, int zoomCenterY)
{
if(zoomAmount == 0) return;
if(zoomAmount < 0)
{
zoomOut(-zoomAmount, zoomCenterX, zoomCenterY);
return;
}
if(seedA != cacheA || seedB != cacheB)
{
generate(rng.nextLong());
}
zoomStartX = Math.min(Math.max(
(zoomStartX + zoomCenterX - (width >> 1) << zoomAmount),
width >> 1), (width << zoom + zoomAmount) - (width >> 1));
// int oldZoomY = zoomStartY;
zoomStartY = Math.min(Math.max(
(zoomStartY + zoomCenterY - (height >> 1) << zoomAmount),
height >> 1), (height << zoom + zoomAmount) - (height >> 1));
// System.out.printf("zoomIn, zoomStartX: %d, zoomStartY: %d, oldZoomY: %d, unedited: %d, upperCap: %d\n", zoomStartX, zoomStartY,
// oldZoomY, (oldZoomY + zoomCenterY - (height >> 1) << zoomAmount), (height << zoom + zoomAmount) - (height >> 1));
zoom += zoomAmount;
if(startCacheX.isEmpty())
{
startCacheX.add(0);
startCacheY.add(0);
}
else {
startCacheX.add(Math.min(Math.max(startCacheX.peek() + (zoomCenterX >> zoom - 1) - (width >> zoom + 1),
0), width - (width >> zoom)));
startCacheY.add(Math.min(Math.max(startCacheY.peek() + (zoomCenterY >> zoom - 1) - (height >> zoom + 1),
0), height - (height >> zoom)));
}
regenerate(startX = (zoomStartX >> zoom) - (width >> 1 + zoom), startY = (zoomStartY >> zoom) - (height >> 1 + zoom),
//startCacheX.peek(), startCacheY.peek(),
usedWidth = width >> zoom, usedHeight = height >> zoom,
landModifier, coolingModifier, cacheA, cacheB);
rng.setState(cacheA, cacheB);
}
protected abstract void regenerate(int startX, int startY, int usedWidth, int usedHeight,
double landMod, double coolMod, int stateA, int stateB);
/**
* Given a latitude and longitude in radians (the conventional way of describing points on a globe), this gets the
* (x,y) Coord on the map projection this generator uses that corresponds to the given lat-lon coordinates. If this
* generator does not represent a globe (if it is toroidal, for instance) or if there is no "good way" to calculate
* the projection for a given lat-lon coordinate, this returns null. The default implementation always returns null.
* If this is a supported operation and the parameters are valid, this returns a Coord with x between 0 and
* {@link #width}, and y between 0 and {@link #height}, both exclusive. Automatically wraps the Coord's values using
* {@link #wrapX(int, int)} and {@link #wrapY(int, int)}.
* @param latitude the latitude, from {@code Math.PI * -0.5} to {@code Math.PI * 0.5}
* @param longitude the longitude, from {@code 0.0} to {@code Math.PI * 2.0}
* @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
*/
public Coord project(double latitude, double longitude)
{
return null;
}
public int codeHeight(final double high)
{
if(high < deepWaterUpper)
return 0;
if(high < mediumWaterUpper)
return 1;
if(high < shallowWaterUpper)
return 2;
if(high < coastalWaterUpper)
return 3;
if(high < sandUpper)
return 4;
if(high < grassUpper)
return 5;
if(high < forestUpper)
return 6;
if(high < rockUpper)
return 7;
return 8;
}
protected final int decodeX(final int coded)
{
return coded % width;
}
protected final int decodeY(final int coded)
{
return coded / width;
}
public int wrapX(final int x, final int y) {
return (x + width) % width;
}
public int wrapY(final int x, final int y) {
return (y + height) % height;
}
// private static final Direction[] reuse = new Direction[6];
// private void appendDirToShuffle(RNG rng) {
// rng.randomPortion(Direction.CARDINALS, reuse);
// reuse[rng.next(2)] = Direction.DIAGONALS[rng.next(2)];
// reuse[4] = Direction.DIAGONALS[rng.next(2)];
// reuse[5] = Direction.OUTWARDS[rng.next(3)];
// }
// protected void addRivers()
// {
// landData.refill(heightCodeData, 4, 999);
// long rebuildState = rng.nextLong();
// //workingData.allOn();
// //.empty().insertRectangle(8, 8, width - 16, height - 16);
// riverData.empty().refill(heightCodeData, 6, 100);
// riverData.quasiRandomRegion(0.0036);
// int[] starts = riverData.asTightEncoded();
// int len = starts.length, currentPos, choice, adjX, adjY, currX, currY, tcx, tcy, stx, sty, sbx, sby;
// riverData.clear();
// lakeData.clear();
// PER_RIVER:
// for (int i = 0; i < len; i++) {
// workingData.clear();
// currentPos = starts[i];
// stx = tcx = currX = decodeX(currentPos);
// sty = tcy = currY = decodeY(currentPos);
// while (true) {
//
// double best = 999999;
// choice = -1;
// appendDirToShuffle(rng);
//
// for (int d = 0; d < 5; d++) {
// adjX = wrapX(currX + reuse[d].deltaX);
// /*
// if (adjX < 0 || adjX >= width)
// {
// if(rng.next(4) == 0)
// riverData.or(workingData);
// continue PER_RIVER;
// }*/
// adjY = wrapY(currY + reuse[d].deltaY);
// if (heightData[adjX][adjY] < best && !workingData.contains(adjX, adjY)) {
// best = heightData[adjX][adjY];
// choice = d;
// tcx = adjX;
// tcy = adjY;
// }
// }
// currX = tcx;
// currY = tcy;
// if (best >= heightData[stx][sty]) {
// tcx = rng.next(2);
// adjX = wrapX(currX + ((tcx & 1) << 1) - 1);
// adjY = wrapY(currY + (tcx & 2) - 1);
// lakeData.insert(currX, currY);
// lakeData.insert(wrapX(currX+1), currY);
// lakeData.insert(wrapX(currX-1), currY);
// lakeData.insert(currX, wrapY(currY+1));
// lakeData.insert(currX, wrapY(currY-1));
//
// if(heightCodeData[adjX][adjY] <= 3) {
// riverData.or(workingData);
// continue PER_RIVER;
// }
// else if((heightData[adjX][adjY] -= 0.0002) < 0.0) {
// if (rng.next(3) == 0)
// riverData.or(workingData);
// continue PER_RIVER;
// }
// tcx = rng.next(2);
// adjX = wrapX(currX + ((tcx & 1) << 1) - 1);
// adjY = wrapY(currY + (tcx & 2) - 1);
// if(heightCodeData[adjX][adjY] <= 3) {
// riverData.or(workingData);
// continue PER_RIVER;
// }
// else if((heightData[adjX][adjY] -= 0.0002) < 0.0) {
// if (rng.next(3) == 0)
// riverData.or(workingData);
// continue PER_RIVER;
// }
// }
// if(choice != -1 && reuse[choice].isDiagonal())
// {
// tcx = wrapX(currX - reuse[choice].deltaX);
// tcy = wrapY(currY - reuse[choice].deltaY);
// if(heightData[tcx][currY] <= heightData[currX][tcy] && !workingData.contains(tcx, currY))
// {
// if(heightCodeData[tcx][currY] < 3 || riverData.contains(tcx, currY))
// {
// riverData.or(workingData);
// continue PER_RIVER;
// }
// workingData.insert(tcx, currY);
// }
// else if(!workingData.contains(currX, tcy))
// {
// if(heightCodeData[currX][tcy] < 3 || riverData.contains(currX, tcy))
// {
// riverData.or(workingData);
// continue PER_RIVER;
// }
// workingData.insert(currX, tcy);
//
// }
// }
// if(heightCodeData[currX][currY] < 3 || riverData.contains(currX, currY))
// {
// riverData.or(workingData);
// continue PER_RIVER;
// }
// workingData.insert(currX, currY);
// }
// }
//
// GreasedRegion tempData = new GreasedRegion(width, height);
// int riverCount = riverData.size() >> 4, currentMax = riverCount >> 3, idx = 0, prevChoice;
// for (int h = 5; h < 9; h++) { //, currentMax += riverCount / 18
// workingData.empty().refill(heightCodeData, h).and(riverData);
// RIVER:
// for (int j = 0; j < currentMax && idx < riverCount; j++) {
// double vdc = VanDerCorputQRNG.weakDetermine(idx++), best = -999999;
// currentPos = workingData.atFractionTight(vdc);
// if(currentPos < 0)
// break;
// stx = sbx = tcx = currX = decodeX(currentPos);
// sty = sby = tcy = currY = decodeY(currentPos);
// appendDirToShuffle(rng);
// choice = -1;
// prevChoice = -1;
// for (int d = 0; d < 5; d++) {
// adjX = wrapX(currX + reuse[d].deltaX);
// adjY = wrapY(currY + reuse[d].deltaY);
// if (heightData[adjX][adjY] > best) {
// best = heightData[adjX][adjY];
// prevChoice = choice;
// choice = d;
// sbx = tcx;
// sby = tcy;
// tcx = adjX;
// tcy = adjY;
// }
// }
// currX = sbx;
// currY = sby;
// if (prevChoice != -1 && heightCodeData[currX][currY] >= 4) {
// if (reuse[prevChoice].isDiagonal()) {
// tcx = wrapX(currX - reuse[prevChoice].deltaX);
// tcy = wrapY(currY - reuse[prevChoice].deltaY);
// if (heightData[tcx][currY] <= heightData[currX][tcy]) {
// if(heightCodeData[tcx][currY] < 3)
// {
// riverData.or(tempData);
// continue;
// }
// tempData.insert(tcx, currY);
// }
// else
// {
// if(heightCodeData[currX][tcy] < 3)
// {
// riverData.or(tempData);
// continue;
// }
// tempData.insert(currX, tcy);
// }
// }
// if(heightCodeData[currX][currY] < 3)
// {
// riverData.or(tempData);
// continue;
// }
// tempData.insert(currX, currY);
// }
//
// while (true) {
// best = -999999;
// appendDirToShuffle(rng);
// choice = -1;
// for (int d = 0; d < 6; d++) {
// adjX = wrapX(currX + reuse[d].deltaX);
// adjY = wrapY(currY + reuse[d].deltaY);
// if (heightData[adjX][adjY] > best && !riverData.contains(adjX, adjY)) {
// best = heightData[adjX][adjY];
// choice = d;
// sbx = adjX;
// sby = adjY;
// }
// }
// currX = sbx;
// currY = sby;
// if (choice != -1) {
// if (reuse[choice].isDiagonal()) {
// tcx = wrapX(currX - reuse[choice].deltaX);
// tcy = wrapY(currY - reuse[choice].deltaY);
// if (heightData[tcx][currY] <= heightData[currX][tcy]) {
// if(heightCodeData[tcx][currY] < 3)
// {
// riverData.or(tempData);
// continue RIVER;
// }
// tempData.insert(tcx, currY);
// }
// else
// {
// if(heightCodeData[currX][tcy] < 3)
// {
// riverData.or(tempData);
// continue RIVER;
// }
// tempData.insert(currX, tcy);
// }
// }
// if(heightCodeData[currX][currY] < 3)
// {
// riverData.or(tempData);
// continue RIVER;
// }
// tempData.insert(currX, currY);
// }
// else
// {
// riverData.or(tempData);
// tempData.clear();
// continue RIVER;
// }
// if (best <= heightData[stx][sty] || heightData[currX][currY] > rng.nextDouble(280.0)) {
// riverData.or(tempData);
// tempData.clear();
// if(heightCodeData[currX][currY] < 3)
// continue RIVER;
// lakeData.insert(currX, currY);
// sbx = rng.next(8);
// sbx &= sbx >>> 4;
// if ((sbx & 1) == 0)
// lakeData.insert(wrapX(currX + 1), currY);
// if ((sbx & 2) == 0)
// lakeData.insert(wrapX(currX - 1), currY);
// if ((sbx & 4) == 0)
// lakeData.insert(currX, wrapY(currY + 1));
// if ((sbx & 8) == 0)
// lakeData.insert(currX, wrapY(currY - 1));
// sbx = rng.next(2);
// lakeData.insert(wrapX(currX + (-(sbx & 1) | 1)), wrapY(currY + ((sbx & 2) - 1))); // random diagonal
// lakeData.insert(currX, wrapY(currY + ((sbx & 2) - 1))); // ortho next to random diagonal
// lakeData.insert(wrapX(currX + (-(sbx & 1) | 1)), currY); // ortho next to random diagonal
//
// continue RIVER;
// }
// }
// }
//
// }
//
// rng.setState(rebuildState);
// }
public interface BiomeMapper
{
/**
* Gets the most relevant biome code for a given x,y point on the map. Some mappers can store more than one
* biome at a location, but only the one with the highest influence will be returned by this method. Biome codes
* are always ints, and are typically between 0 and 60, both inclusive; they are meant to be used as indices
* into a table of names or other objects that identify a biome, accessible via {@link #getBiomeNameTable()}.
* Although different classes may define biome codes differently, they should all be able to be used as indices
* into the String array returned by getBiomeNameTable().
* @param x the x-coordinate on the map
* @param y the y-coordinate on the map
* @return an int that can be used as an index into the array returned by {@link #getBiomeNameTable()}
*/
int getBiomeCode(int x, int y);
/**
* Gets a heat code for a given x,y point on a map, usually as an int between 0 and 5 inclusive. Some
* implementations may use more or less detail for heat codes, but 0 is always the coldest code used, and the
* highest value this can return for a given implementation refers to the hottest code used.
* @param x the x-coordinate on the map
* @param y the y-coordinate on the map
* @return an int that can be used to categorize how hot an area is, with 0 as coldest
*/
int getHeatCode(int x, int y);
/**
* Gets a moisture code for a given x,y point on a map, usually as an int between 0 and 5 inclusive. Some
* implementations may use more or less detail for moisture codes, but 0 is always the driest code used, and the
* highest value this can return for a given implementation refers to the wettest code used. Some
* implementations may allow seasonal change in moisture, e.g. monsoon seasons, to be modeled differently from
* average precipitation in an area, but the default assumption is that this describes the average amount of
* moisture (rain, humidity, and possibly snow/hail or other precipitation) that an area receives annually.
* @param x the x-coordinate on the map
* @param y the y-coordinate on the map
* @return an int that can be used to categorize how much moisture an area tends to receive, with 0 as driest
*/
int getMoistureCode(int x, int y);
/**
* Gets a String array where biome codes can be used as indices to look up a name for the biome they refer to. A
* sample table is in {@link SimpleBiomeMapper#biomeTable}; the 61-element array format documented for that
* field is encouraged for implementing classes if they use 6 levels of heat and 6 levels of moisture, and track
* rivers, coastlines, lakes, and oceans as potentially different types of terrain. Biome codes can be obtained
* with {@link #getBiomeCode(int, int)}, or for some implementing classes other methods may provide more
* detailed information.
* @return a String array that often contains 61 elements, to be used with biome codes as indices.
*/
String[] getBiomeNameTable();
/**
* Analyzes the last world produced by the given WorldMapGenerator and uses all of its generated information to
* assign biome codes for each cell (along with heat and moisture codes). After calling this, biome codes can be
* retrieved with {@link #getBiomeCode(int, int)} and used as indices into {@link #getBiomeNameTable()} or a
* custom biome table.
* @param world a WorldMapGenerator that should have generated at least one map; it may be at any zoom
*/
void makeBiomes(WorldMapGenerator world);
}
/**
* A way to get biome information for the cells on a map when you only need a single value to describe a biome, such
* as "Grassland" or "TropicalRainforest".
*
* To use: 1, Construct a SimpleBiomeMapper (constructor takes no arguments). 2, call
* {@link #makeBiomes(WorldMapGenerator)} with a WorldMapGenerator that has already produced at least one world map.
* 3, get biome codes from the {@link #biomeCodeData} field, where a code is an int that can be used as an index
* into the {@link #biomeTable} static field to get a String name for a biome type, or used with an alternate biome
* table of your design. Biome tables in this case are 61-element arrays organized into groups of 6 elements, with
* the last element reserved for empty space where the map doesn't cover (as with some map projections). Each
* group goes from the coldest temperature first to the warmest temperature last in the group. The first group of 6
* contains the dryest biomes, the next 6 are medium-dry, the next are slightly-dry, the next slightly-wet, then
* medium-wet, then wettest. After this first block of dry-to-wet groups, there is a group of 6 for coastlines, a
* group of 6 for rivers, a group of 6 for lakes, a group of 6 for oceans, and then one element for space outside
* the map. The last element, with code 60, is by convention the String "Empty", but normally the code should be
* enough to tell that a space is off-map. This also assigns moisture codes and heat codes from 0 to 5 for each
* cell, which may be useful to simplify logic that deals with those factors.
*/
public static class SimpleBiomeMapper implements BiomeMapper
{
/**
* The heat codes for the analyzed map, from 0 to 5 inclusive, with 0 coldest and 5 hottest.
*/
public int[][] heatCodeData,
/**
* The moisture codes for the analyzed map, from 0 to 5 inclusive, with 0 driest and 5 wettest.
*/
moistureCodeData,
/**
* The biome codes for the analyzed map, from 0 to 60 inclusive. You can use {@link #biomeTable} to look up
* String names for biomes, or construct your own table as you see fit (see docs in {@link SimpleBiomeMapper}).
*/
biomeCodeData;
@Override
public int getBiomeCode(int x, int y) {
return biomeCodeData[x][y];
}
@Override
public int getHeatCode(int x, int y) {
return heatCodeData[x][y];
}
@Override
public int getMoistureCode(int x, int y) {
return moistureCodeData[x][y];
}
/**
* Gets a String array where biome codes can be used as indices to look up a name for the biome they refer to.
* This table uses 6 levels of heat and 6 levels of moisture, and tracks rivers, coastlines, lakes, and oceans
* as potentially different types of terrain. Biome codes can be obtained with {@link #getBiomeCode(int, int)}.
* This method returns a direct reference to {@link #biomeTable}, so modifying the returned array is discouraged
* (you should implement {@link BiomeMapper} using this class as a basis if you want to change its size).
* @return a direct reference to {@link #biomeTable}, a String array containing names of biomes
*/
@Override
public String[] getBiomeNameTable() {
return biomeTable;
}
public static final double
coldestValueLower = 0.0, coldestValueUpper = 0.15, // 0
colderValueLower = 0.15, colderValueUpper = 0.31, // 1
coldValueLower = 0.31, coldValueUpper = 0.5, // 2
warmValueLower = 0.5, warmValueUpper = 0.69, // 3
warmerValueLower = 0.69, warmerValueUpper = 0.85, // 4
warmestValueLower = 0.85, warmestValueUpper = 1.0, // 5
driestValueLower = 0.0, driestValueUpper = 0.27, // 0
drierValueLower = 0.27, drierValueUpper = 0.4, // 1
dryValueLower = 0.4, dryValueUpper = 0.6, // 2
wetValueLower = 0.6, wetValueUpper = 0.8, // 3
wetterValueLower = 0.8, wetterValueUpper = 0.9, // 4
wettestValueLower = 0.9, wettestValueUpper = 1.0; // 5
/**
* The default biome table to use with biome codes from {@link #biomeCodeData}. Biomes are assigned based on
* heat and moisture for the first 36 of 61 elements (coldest to warmest for each group of 6, with the first
* group as the dryest and the last group the wettest), then the next 6 are for coastlines (coldest to warmest),
* then rivers (coldest to warmest), then lakes (coldest to warmest), then oceans (coldest to warmest), and
* lastly a single "biome" for empty space outside the map (meant for projections that don't fill a rectangle).
*/
public static final String[] biomeTable = {
//COLDEST //COLDER //COLD //HOT //HOTTER //HOTTEST
"Ice", "Ice", "Grassland", "Desert", "Desert", "Desert", //DRYEST
"Ice", "Tundra", "Grassland", "Grassland", "Desert", "Desert", //DRYER
"Ice", "Tundra", "Woodland", "Woodland", "Savanna", "Desert", //DRY
"Ice", "Tundra", "SeasonalForest", "SeasonalForest", "Savanna", "Savanna", //WET
"Ice", "Tundra", "BorealForest", "TemperateRainforest", "TropicalRainforest", "Savanna", //WETTER
"Ice", "BorealForest", "BorealForest", "TemperateRainforest", "TropicalRainforest", "TropicalRainforest", //WETTEST
"Rocky", "Rocky", "Beach", "Beach", "Beach", "Beach", //COASTS
"Ice", "River", "River", "River", "River", "River", //RIVERS
"Ice", "River", "River", "River", "River", "River", //LAKES
"Ocean", "Ocean", "Ocean", "Ocean", "Ocean", "Ocean", //OCEAN
"Empty", //SPACE
};
/**
* Simple constructor; pretty much does nothing. Make sure to call {@link #makeBiomes(WorldMapGenerator)} before
* using fields like {@link #biomeCodeData}.
*/
public SimpleBiomeMapper()
{
heatCodeData = null;
moistureCodeData = null;
biomeCodeData = null;
}
/**
* Analyzes the last world produced by the given WorldMapGenerator and uses all of its generated information to
* assign biome codes for each cell (along with heat and moisture codes). After calling this, biome codes can be
* taken from {@link #biomeCodeData} and used as indices into {@link #biomeTable} or a custom biome table.
* @param world a WorldMapGenerator that should have generated at least one map; it may be at any zoom
*/
@Override
public void makeBiomes(WorldMapGenerator world) {
if(world == null || world.width <= 0 || world.height <= 0)
return;
if(heatCodeData == null || (heatCodeData.length != world.width || heatCodeData[0].length != world.height))
heatCodeData = new int[world.width][world.height];
if(moistureCodeData == null || (moistureCodeData.length != world.width || moistureCodeData[0].length != world.height))
moistureCodeData = new int[world.width][world.height];
if(biomeCodeData == null || (biomeCodeData.length != world.width || biomeCodeData[0].length != world.height))
biomeCodeData = new int[world.width][world.height];
final double i_hot = (world.maxHeat == world.minHeat) ? 1.0 : 1.0 / (world.maxHeat - world.minHeat);
for (int x = 0; x < world.width; x++) {
for (int y = 0; y < world.height; y++) {
final double hot = (world.heatData[x][y] - world.minHeat) * i_hot, moist = world.moistureData[x][y];
final int heightCode = world.heightCodeData[x][y];
if(heightCode == 1000) {
biomeCodeData[x][y] = 60;
continue;
}
int hc, mc;
boolean isLake = false,// world.generateRivers && heightCode >= 4 && fresh > 0.65 && fresh + moist * 2.35 > 2.75,//world.partialLakeData.contains(x, y) && heightCode >= 4,
isRiver = false;// world.generateRivers && !isLake && heightCode >= 4 && fresh > 0.55 && fresh + moist * 2.2 > 2.15;//world.partialRiverData.contains(x, y) && heightCode >= 4;
if(heightCode < 4) {
mc = 9;
}
else if (moist > wetterValueUpper) {
mc = 5;
} else if (moist > wetValueUpper) {
mc = 4;
} else if (moist > dryValueUpper) {
mc = 3;
} else if (moist > drierValueUpper) {
mc = 2;
} else if (moist > driestValueUpper) {
mc = 1;
} else {
mc = 0;
}
if (hot > warmerValueUpper) {
hc = 5;
} else if (hot > warmValueUpper) {
hc = 4;
} else if (hot > coldValueUpper) {
hc = 3;
} else if (hot > colderValueUpper) {
hc = 2;
} else if (hot > coldestValueUpper) {
hc = 1;
} else {
hc = 0;
}
heatCodeData[x][y] = hc;
moistureCodeData[x][y] = mc;
biomeCodeData[x][y] = heightCode < 4 ? hc + 54 // 54 == 9 * 6, 9 is used for Ocean groups
: isLake ? hc + 48 : (isRiver ? hc + 42 : ((heightCode == 4) ? hc + 36 : hc + mc * 6));
}
}
}
}
/**
* 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
* main biome types, such as "Grassland" or "TropicalRainforest", with the biomes varying in weight between areas.
*
* To use: 1, Construct a DetailedBiomeMapper (constructor takes no arguments). 2, call
* {@link #makeBiomes(WorldMapGenerator)} with a WorldMapGenerator that has already produced at least one world map.
* 3, get biome codes from the {@link #biomeCodeData} field, where a code is an int that can be used with the
* extract methods in this class to get various information from it (these are {@link #extractBiomeA(int)},
* {@link #extractBiomeB(int)}, {@link #extractPartA(int)}, {@link #extractPartB(int)}, and
* {@link #extractMixAmount(int)}). You can get predefined names for biomes using the extractBiome methods (these
* names can be changed in {@link #biomeTable}), or raw indices into some (usually 61-element) collection or array
* with the extractPart methods. The extractMixAmount() method gets a float that is the amount by which biome B
* affects biome A; if this is higher than 0.5, then biome B is the "dominant" biome in the area.
*/
public static class DetailedBiomeMapper implements BiomeMapper
{
/**
* The heat codes for the analyzed map, from 0 to 5 inclusive, with 0 coldest and 5 hottest.
*/
public int[][] heatCodeData,
/**
* The moisture codes for the analyzed map, from 0 to 5 inclusive, with 0 driest and 5 wettest.
*/
moistureCodeData,
/**
* The biome codes for the analyzed map, using one int to store the codes for two biomes and the degree by which
* the second biome affects the first. These codes can be used with methods in this class like
* {@link #extractBiomeA(int)}, {@link #extractBiomeB(int)}, and {@link #extractMixAmount(int)} to find the two
* dominant biomes in an area, called biome A and biome B, and the mix amount, for finding how much biome B
* affects biome A.
*/
biomeCodeData;
@Override
public int getBiomeCode(int x, int y) {
int code = biomeCodeData[x][y];
if(code < 0x2000000) return code & 1023;
return (code >>> 10) & 1023;
}
@Override
public int getHeatCode(int x, int y) {
return heatCodeData[x][y];
}
@Override
public int getMoistureCode(int x, int y) {
return moistureCodeData[x][y];
}
/**
* Gets a String array where biome codes can be used as indices to look up a name for the biome they refer to.
* This table uses 6 levels of heat and 6 levels of moisture, and tracks rivers, coastlines, lakes, and oceans
* as potentially different types of terrain. Biome codes can be obtained with {@link #getBiomeCode(int, int)}.
* This method returns a direct reference to {@link #biomeTable}, so modifying the returned array is discouraged
* (you should implement {@link BiomeMapper} using this class as a basis if you want to change its size).
* @return a direct reference to {@link #biomeTable}, a String array containing names of biomes
*/
@Override
public String[] getBiomeNameTable() {
return biomeTable;
}
public static final double
coldestValueLower = 0.0, coldestValueUpper = 0.15, // 0
colderValueLower = 0.15, colderValueUpper = 0.31, // 1
coldValueLower = 0.31, coldValueUpper = 0.5, // 2
warmValueLower = 0.5, warmValueUpper = 0.69, // 3
warmerValueLower = 0.69, warmerValueUpper = 0.85, // 4
warmestValueLower = 0.85, warmestValueUpper = 1.0, // 5
driestValueLower = 0.0, driestValueUpper = 0.27, // 0
drierValueLower = 0.27, drierValueUpper = 0.4, // 1
dryValueLower = 0.4, dryValueUpper = 0.6, // 2
wetValueLower = 0.6, wetValueUpper = 0.8, // 3
wetterValueLower = 0.8, wetterValueUpper = 0.9, // 4
wettestValueLower = 0.9, wettestValueUpper = 1.0; // 5
/**
* The default biome table to use with parts of biome codes from {@link #biomeCodeData}. Biomes are assigned by
* heat and moisture for the first 36 of 61 elements (coldest to warmest for each group of 6, with the first
* group as the dryest and the last group the wettest), then the next 6 are for coastlines (coldest to warmest),
* then rivers (coldest to warmest), then lakes (coldest to warmest). The last is reserved for empty space.
*
* Unlike with {@link SimpleBiomeMapper}, you cannot use a biome code directly from biomeCodeData as an index
* into this in almost any case; you should pass the biome code to one of the extract methods.
* {@link #extractBiomeA(int)} or {@link #extractBiomeB(int)} will work if you want a biome name, or
* {@link #extractPartA(int)} or {@link #extractPartB(int)} should be used if you want a non-coded int that
* represents one of the biomes' indices into something like this. You can also get the amount by which biome B
* is affecting biome A with {@link #extractMixAmount(int)}.
*/
public static final String[] biomeTable = {
//COLDEST //COLDER //COLD //HOT //HOTTER //HOTTEST
"Ice", "Ice", "Grassland", "Desert", "Desert", "Desert", //DRYEST
"Ice", "Tundra", "Grassland", "Grassland", "Desert", "Desert", //DRYER
"Ice", "Tundra", "Woodland", "Woodland", "Savanna", "Desert", //DRY
"Ice", "Tundra", "SeasonalForest", "SeasonalForest", "Savanna", "Savanna", //WET
"Ice", "Tundra", "BorealForest", "TemperateRainforest", "TropicalRainforest", "Savanna", //WETTER
"Ice", "BorealForest", "BorealForest", "TemperateRainforest", "TropicalRainforest", "TropicalRainforest", //WETTEST
"Rocky", "Rocky", "Beach", "Beach", "Beach", "Beach", //COASTS
"Ice", "River", "River", "River", "River", "River", //RIVERS
"Ice", "River", "River", "River", "River", "River", //LAKES
"Ocean", "Ocean", "Ocean", "Ocean", "Ocean", "Ocean", //OCEAN
"Empty", //SPACE
};
/**
* Gets the int stored in part A of the given biome code, which can be used as an index into other collections.
* This int should almost always range from 0 to 60 (both inclusive), so collections this is used as an index
* for should have a length of at least 61.
* @param biomeCode a biome code that was probably received from {@link #biomeCodeData}
* @return an int stored in the biome code's part A; almost always between 0 and 60, inclusive.
*/
public int extractPartA(int biomeCode)
{
return biomeCode & 1023;
}
/**
* Gets a String from {@link #biomeTable} that names the appropriate biome in part A of the given biome code.
* @param biomeCode a biome code that was probably received from {@link #biomeCodeData}
* @return a String that names the biome in part A of biomeCode, or "Empty" if none can be found
*/
public String extractBiomeA(int biomeCode)
{
biomeCode &= 1023;
if(biomeCode < 60)
return biomeTable[biomeCode];
return "Empty";
}
/**
* Gets the int stored in part B of the given biome code, which can be used as an index into other collections.
* This int should almost always range from 0 to 60 (both inclusive), so collections this is used as an index
* for should have a length of at least 61.
* @param biomeCode a biome code that was probably received from {@link #biomeCodeData}
* @return an int stored in the biome code's part B; almost always between 0 and 60, inclusive.
*/
public int extractPartB(int biomeCode)
{
return (biomeCode >>> 10) & 1023;
}
/**
* Gets a String from {@link #biomeTable} that names the appropriate biome in part B of the given biome code.
* @param biomeCode a biome code that was probably received from {@link #biomeCodeData}
* @return a String that names the biome in part B of biomeCode, or "Ocean" if none can be found
*/
public String extractBiomeB(int biomeCode)
{
biomeCode = (biomeCode >>> 10) & 1023;
if(biomeCode < 60)
return biomeTable[biomeCode];
return "Empty";
}
/**
* This gets the portion of a biome code that represents the amount of mixing between two biomes.
* Biome codes are normally obtained from the {@link #biomeCodeData} field, and aren't very usable on their own
* without calling methods like this, {@link #extractBiomeA(int)}, and {@link #extractBiomeB(int)}. This returns
* a float between 0.0f (inclusive) and 1.0f (exclusive), with 0.0f meaning biome B has no effect on an area and
* biome A is the only one used, 0.5f meaning biome A and biome B have equal effect, and 0.75f meaning biome B
* has most of the effect, three-fourths of the area, and biome A has less, one-fourth of the area.
* @param biomeCode a biome code that was probably received from {@link #biomeCodeData}
* @return a float between 0.0f (inclusive) and 1.0f (exclusive) representing mixing of biome B into biome A
*/
public float extractMixAmount(int biomeCode)
{
return (biomeCode >>> 20) * 0x1p-10f;
}
/**
* Simple constructor; pretty much does nothing. Make sure to call {@link #makeBiomes(WorldMapGenerator)} before
* using fields like {@link #biomeCodeData}.
*/
public DetailedBiomeMapper()
{
heatCodeData = null;
moistureCodeData = null;
biomeCodeData = null;
}
/**
* Analyzes the last world produced by the given WorldMapGenerator and uses all of its generated information to
* assign biome codes for each cell (along with heat and moisture codes). After calling this, biome codes can be
* taken from {@link #biomeCodeData} and used with methods in this class like {@link #extractBiomeA(int)},
* {@link #extractBiomeB(int)}, and {@link #extractMixAmount(int)} to find the two dominant biomes in an area,
* called biome A and biome B, and the mix amount, for finding how much biome B affects biome A.
* @param world a WorldMapGenerator that should have generated at least one map; it may be at any zoom
*/
@Override
public void makeBiomes(WorldMapGenerator world) {
if(world == null || world.width <= 0 || world.height <= 0)
return;
if(heatCodeData == null || (heatCodeData.length != world.width || heatCodeData[0].length != world.height))
heatCodeData = new int[world.width][world.height];
if(moistureCodeData == null || (moistureCodeData.length != world.width || moistureCodeData[0].length != world.height))
moistureCodeData = new int[world.width][world.height];
if(biomeCodeData == null || (biomeCodeData.length != world.width || biomeCodeData[0].length != world.height))
biomeCodeData = new int[world.width][world.height];
final int[][] heightCodeData = world.heightCodeData;
final double[][] heatData = world.heatData, moistureData = world.moistureData, heightData = world.heightData;
int hc, mc, heightCode, bc;
double hot, moist, high, i_hot = 1.0 / world.maxHeat, fresh;
for (int x = 0; x < world.width; x++) {
for (int y = 0; y < world.height; y++) {
heightCode = heightCodeData[x][y];
if(heightCode == 1000) {
biomeCodeData[x][y] = 61;
continue;
}
hot = heatData[x][y];
moist = moistureData[x][y];
high = heightData[x][y];
// fresh = world.freshwaterData[x][y];
boolean isLake = false,//world.generateRivers && heightCode >= 4 && fresh > 0.65 && fresh + moist * 2.35 > 2.75,//world.partialLakeData.contains(x, y) && heightCode >= 4,
isRiver = false;//world.generateRivers && !isLake && heightCode >= 4 && fresh > 0.55 && fresh + moist * 2.2 > 2.15;//world.partialRiverData.contains(x, y) && heightCode >= 4;
if (moist >= (wettestValueUpper - (wetterValueUpper - wetterValueLower) * 0.2)) {
mc = 5;
} else if (moist >= (wetterValueUpper - (wetValueUpper - wetValueLower) * 0.2)) {
mc = 4;
} else if (moist >= (wetValueUpper - (dryValueUpper - dryValueLower) * 0.2)) {
mc = 3;
} else if (moist >= (dryValueUpper - (drierValueUpper - drierValueLower) * 0.2)) {
mc = 2;
} else if (moist >= (drierValueUpper - (driestValueUpper) * 0.2)) {
mc = 1;
} else {
mc = 0;
}
if (hot >= (warmestValueUpper - (warmerValueUpper - warmerValueLower) * 0.2) * i_hot) {
hc = 5;
} else if (hot >= (warmerValueUpper - (warmValueUpper - warmValueLower) * 0.2) * i_hot) {
hc = 4;
} else if (hot >= (warmValueUpper - (coldValueUpper - coldValueLower) * 0.2) * i_hot) {
hc = 3;
} else if (hot >= (coldValueUpper - (colderValueUpper - colderValueLower) * 0.2) * i_hot) {
hc = 2;
} else if (hot >= (colderValueUpper - (coldestValueUpper) * 0.2) * i_hot) {
hc = 1;
} else {
hc = 0;
}
heatCodeData[x][y] = hc;
moistureCodeData[x][y] = mc;
bc = heightCode < 4 ? hc + 54 // 54 == 9 * 6, 9 is used for Ocean groups
: isLake ? hc + 48 : (isRiver ? hc + 42 : ((heightCode == 4) ? hc + 36 : hc + mc * 6));
if(heightCode < 4) {
mc = 9;
}
else if (moist >= (wetterValueUpper + (wettestValueUpper - wettestValueLower) * 0.2)) {
mc = 5;
} else if (moist >= (wetValueUpper + (wetterValueUpper - wetterValueLower) * 0.2)) {
mc = 4;
} else if (moist >= (dryValueUpper + (wetValueUpper - wetValueLower) * 0.2)) {
mc = 3;
} else if (moist >= (drierValueUpper + (dryValueUpper - dryValueLower) * 0.2)) {
mc = 2;
} else if (moist >= (driestValueUpper + (drierValueUpper - drierValueLower) * 0.2)) {
mc = 1;
} else {
mc = 0;
}
if (hot >= (warmerValueUpper + (warmestValueUpper - warmestValueLower) * 0.2) * i_hot) {
hc = 5;
} else if (hot >= (warmValueUpper + (warmerValueUpper - warmerValueLower) * 0.2) * i_hot) {
hc = 4;
} else if (hot >= (coldValueUpper + (warmValueUpper - warmValueLower) * 0.2) * i_hot) {
hc = 3;
} else if (hot >= (colderValueUpper + (coldValueUpper - coldValueLower) * 0.2) * i_hot) {
hc = 2;
} else if (hot >= (coldestValueUpper + (colderValueUpper - colderValueLower) * 0.2) * i_hot) {
hc = 1;
} else {
hc = 0;
}
bc |= (hc + mc * 6) << 10;
if(heightCode < 4)
biomeCodeData[x][y] = bc | (int)((heightData[x][y] + 1.0) * 1000.0) << 20;
else if (isRiver || isLake)
biomeCodeData[x][y] = bc | (int)(moist * 358.4 + 665.0) << 20;
else
biomeCodeData[x][y] = bc | (int) ((heightCode == 4)
? (sandUpper - high) * 10240.0 // multiplier affected by changes to sandLower
: NumberTools.sway((high + moist) * (4.1 + high - hot)) * 512 + 512) << 20;
}
}
}
}
/**
* A concrete implementation of {@link WorldMapGenerator} that tiles both east-to-west and north-to-south. It tends
* to not appear distorted like {@link WorldMapGenerator.SphereMap} does in some areas, even though this is inaccurate for a
* rectangular projection of a spherical world (that inaccuracy is likely what players expect in a map, though).
* Example map.
*/
public static class TilingMap extends WorldMapGenerator {
//protected static final double terrainFreq = 1.5, terrainRidgedFreq = 1.3, heatFreq = 2.8, moistureFreq = 2.9, otherFreq = 4.5;
// protected static final double terrainFreq = 1.175, terrainRidgedFreq = 1.3, heatFreq = 2.3, moistureFreq = 2.4, otherFreq = 3.5;
protected static final double terrainFreq = 0.95, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375;
private double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY,
minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY,
minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY;
public final Noise4D terrain, terrainRidged, heat, moisture, otherRidged;
/**
* Constructs a concrete WorldMapGenerator for a map that can be used as a tiling, wrapping east-to-west as well
* as north-to-south. Always makes a 256x256 map.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
* If you were using {@link TilingMap#TilingMap(long, int, int, Noise4D, double)}, then this would be the
* same as passing the parameters {@code 0x1337BABE1337D00DL, 256, 256, FastNoise.instance, 1.0}.
*/
public TilingMap() {
this(0x1337BABE1337D00DL, 256, 256, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used as a tiling, wrapping east-to-west as well
* as north-to-south.
* Takes only the width/height of the map. The initial seed is set to the same large long
* every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and
* height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public TilingMap(int mapWidth, int mapHeight) {
this(0x1337BABE1337D00DL, mapWidth, mapHeight, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used as a tiling, wrapping east-to-west as well
* as north-to-south.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public TilingMap(long initialSeed, int mapWidth, int mapHeight) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used as a tiling, wrapping east-to-west as well
* as north-to-south. Takes an initial seed, the width/height of the map, and a noise generator (a
* {@link Noise4D} implementation, which is usually {@link FastNoise#instance}. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call
* {@link #generate(long)}. The width and height of the map 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
* {@link Noise4D#getNoiseWithSeed(double, double, double, double, long)} will be used to specify the seed many
* times. The detail level, which is the {@code octaveMultiplier} parameter that can be passed to another
* constructor, is always 1.0 with this constructor.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 4D noise, recommended to be {@link FastNoise#instance}
*/
public TilingMap(long initialSeed, int mapWidth, int mapHeight, final Noise4D noiseGenerator) {
this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used as a tiling, wrapping east-to-west as well
* as north-to-south. Takes an initial seed, the width/height of the map, and parameters for noise
* generation (a {@link Noise4D} implementation, which is usually {@link FastNoise#instance}, and a
* multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers
* producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used,
* since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map
* 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
* {@link Noise4D#getNoiseWithSeed(double, double, double, double, long)} will be used to specify the seed many
* times. The {@code octaveMultiplier} parameter should probably be no lower than 0.5, but can be arbitrarily
* high if you're willing to spend much more time on generating detail only noticeable at very high zoom;
* normally 1.0 is fine and may even be too high for maps that don't require zooming.
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 4D noise, almost always {@link FastNoise}
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public TilingMap(long initialSeed, int mapWidth, int mapHeight, final Noise4D noiseGenerator, double octaveMultiplier) {
super(initialSeed, mapWidth, mapHeight);
terrain = new Noise.InverseLayered4D(noiseGenerator, (int) (0.5 + octaveMultiplier * 8), terrainFreq);
terrainRidged = new Noise.Ridged4D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainRidgedFreq);
heat = new Noise.InverseLayered4D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq);
moisture = new Noise.InverseLayered4D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq);
otherRidged = new Noise.Ridged4D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq);
}
protected void regenerate(int startX, int startY, int usedWidth, int usedHeight,
double landMod, double coolMod, int stateA, int stateB)
{
boolean fresh = false;
if(cacheA != stateA || cacheB != stateB || landMod != landModifier || coolMod != coolingModifier)
{
minHeight = Double.POSITIVE_INFINITY;
maxHeight = Double.NEGATIVE_INFINITY;
minHeat0 = Double.POSITIVE_INFINITY;
maxHeat0 = Double.NEGATIVE_INFINITY;
minHeat1 = Double.POSITIVE_INFINITY;
maxHeat1 = Double.NEGATIVE_INFINITY;
minHeat = Double.POSITIVE_INFINITY;
maxHeat = Double.NEGATIVE_INFINITY;
minWet0 = Double.POSITIVE_INFINITY;
maxWet0 = Double.NEGATIVE_INFINITY;
minWet = Double.POSITIVE_INFINITY;
maxWet = Double.NEGATIVE_INFINITY;
cacheA = stateA;
cacheB = stateB;
fresh = true;
}
rng.setState(stateA, stateB);
long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong();
int t;
landModifier = (landMod <= 0) ? rng.nextDouble(0.1875) + 0.99 : landMod;
coolingModifier = (coolMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : coolMod;
double p, q,
ps, pc,
qs, qc,
h, temp,
i_w = 6.283185307179586 / width, i_h = 6.283185307179586 / height,
xPos = startX, yPos = startY, i_uw = usedWidth / (double)width, i_uh = usedHeight / (double)height;
double[] trigTable = new double[width << 1];
for (int x = 0; x < width; x++, xPos += i_uw) {
p = xPos * i_w;
trigTable[x<<1] = NumberTools.sin(p);
trigTable[x<<1|1] = NumberTools.cos(p);
}
for (int y = 0; y < height; y++, yPos += i_uh) {
q = yPos * i_h;
qs = NumberTools.sin(q);
qc = NumberTools.cos(q);
for (int x = 0, xt = 0; x < width; x++) {
ps = trigTable[xt++];//NumberTools.sin(p);
pc = trigTable[xt++];//NumberTools.cos(p);
heightData[x][y] = (h = terrain.getNoiseWithSeed(pc +
terrainRidged.getNoiseWithSeed(pc, ps, qc, qs,seedB - seedA) * 0.25,
ps, qc, qs, seedA) + landModifier - 1.0);
heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps, qc
+ otherRidged.getNoiseWithSeed(pc, ps, qc, qs, seedB + seedC)
, qs, seedB));
moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qc, qs
+ otherRidged.getNoiseWithSeed(pc, ps, qc, qs, seedC + seedA)
, seedC));
minHeightActual = Math.min(minHeightActual, h);
maxHeightActual = Math.max(maxHeightActual, h);
if(fresh) {
minHeight = Math.min(minHeight, h);
maxHeight = Math.max(maxHeight, h);
minHeat0 = Math.min(minHeat0, p);
maxHeat0 = Math.max(maxHeat0, p);
minWet0 = Math.min(minWet0, temp);
maxWet0 = Math.max(maxWet0, temp);
}
}
minHeightActual = Math.min(minHeightActual, minHeight);
maxHeightActual = Math.max(maxHeightActual, maxHeight);
}
double heightDiff = 2.0 / (maxHeightActual - minHeightActual),
heatDiff = 0.8 / (maxHeat0 - minHeat0),
wetDiff = 1.0 / (maxWet0 - minWet0),
hMod,
halfHeight = (height - 1) * 0.5, i_half = 1.0 / halfHeight;
double minHeightActual0 = minHeightActual;
double maxHeightActual0 = maxHeightActual;
yPos = startY;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++, yPos += i_uh) {
temp = Math.abs(yPos - halfHeight) * i_half;
temp *= (2.4 - temp);
temp = 2.2 - temp;
for (int x = 0; x < width; x++) {
// heightData[x][y] = (h = (heightData[x][y] - minHeightActual) * heightDiff - 1.0);
// minHeightActual0 = Math.min(minHeightActual0, h);
// maxHeightActual0 = Math.max(maxHeightActual0, h);
h = heightData[x][y];
heightCodeData[x][y] = (t = codeHeight(h));
hMod = 1.0;
switch (t) {
case 0:
case 1:
case 2:
case 3:
h = 0.4;
hMod = 0.2;
break;
case 6:
h = -0.1 * (h - forestLower - 0.08);
break;
case 7:
h *= -0.25;
break;
case 8:
h *= -0.4;
break;
default:
h *= 0.05;
}
heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp);
if (fresh) {
ps = Math.min(ps, h); //minHeat0
pc = Math.max(pc, h); //maxHeat0
}
}
}
if(fresh)
{
minHeat1 = ps;
maxHeat1 = pc;
}
heatDiff = coolingModifier / (maxHeat1 - minHeat1);
qs = Double.POSITIVE_INFINITY;
qc = Double.NEGATIVE_INFINITY;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff));
moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff);
if (fresh) {
qs = Math.min(qs, h);
qc = Math.max(qc, h);
ps = Math.min(ps, temp);
pc = Math.max(pc, temp);
}
}
}
if(fresh)
{
minHeat = qs;
maxHeat = qc;
minWet = ps;
maxWet = pc;
}
landData.refill(heightCodeData, 4, 999);
/*
if(generateRivers) {
if (fresh) {
addRivers();
riverData.connect8way().thin().thin();
lakeData.connect8way().thin();
partialRiverData.remake(riverData);
partialLakeData.remake(lakeData);
} else {
partialRiverData.remake(riverData);
partialLakeData.remake(lakeData);
int stx = (zoomStartX >> (zoom)) - (width >> 1),
sty = (zoomStartY >> (zoom)) - (height >> 1);
for (int i = 1; i <= zoom; i++) {
// int stx = (startCacheX.get(i) - startCacheX.get(i - 1)) << (i - 1),
// sty = (startCacheY.get(i) - startCacheY.get(i - 1)) << (i - 1);
if ((i & 3) == 3) {
partialRiverData.zoom(stx, sty).connect8way();
partialRiverData.or(workingData.remake(partialRiverData).fringe().quasiRandomRegion(0.4));
partialLakeData.zoom(stx, sty).connect8way();
partialLakeData.or(workingData.remake(partialLakeData).fringe().quasiRandomRegion(0.55));
} else {
partialRiverData.zoom(stx, sty).connect8way().thin();
partialRiverData.or(workingData.remake(partialRiverData).fringe().quasiRandomRegion(0.5));
partialLakeData.zoom(stx, sty).connect8way().thin();
partialLakeData.or(workingData.remake(partialLakeData).fringe().quasiRandomRegion(0.7));
}
}
}
}
*/
}
}
/**
* A concrete implementation of {@link WorldMapGenerator} that distorts the map as it nears the poles, expanding the
* smaller-diameter latitude lines in extreme north and south regions so they take up the same space as the equator;
* this counteracts certain artifacts that are common in Simplex noise world maps by using a 4D noise call to
* generate terrain, using a normal 3D noise call's result as the extra 4th dimension. This generator allows
* choosing a {@link Noise3D}, which is used for most of the generation. This is ideal for projecting onto a 3D
* sphere, which could squash the poles to counteract the stretch this does. You might also want to produce an oval
* map that more-accurately represents the changes in the diameter of a latitude line on a spherical world; you
* should use {@link WorldMapGenerator.EllipticalMap} or {@link WorldMapGenerator.EllipticalHammerMap} for this.
* {@link WorldMapGenerator.HyperellipticalMap} is also a nice option because it can project onto a shape between a
* rectangle (like this class) and an ellipse (like EllipticalMap), with all-round sides.
* Example map, showing distortion
*/
@Beta
public static class SphereMap extends WorldMapGenerator {
protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375;
//protected static final double terrainFreq = 1.65, terrainRidgedFreq = 1.8, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375, riverRidgedFreq = 21.7;
private double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY,
minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY,
minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY;
public final Noise3D terrain, heat, moisture, otherRidged, terrainLayered;
public final double[][] xPositions,
yPositions,
zPositions;
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a
* 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to
* have significantly-exaggerated-in-size features while the equator is not distorted.
* Always makes a 256x128 map.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
* If you were using {@link SphereMap#SphereMap(long, int, int, Noise3D, double)}, then this would be the
* same as passing the parameters {@code 0x1337BABE1337D00DL, 256, 128, FastNoise.instance, 1.0}.
*/
public SphereMap() {
this(0x1337BABE1337D00DL, 256, 128, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a
* 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to
* have significantly-exaggerated-in-size features while the equator is not distorted.
* Takes only the width/height of the map. The initial seed is set to the same large long
* every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and
* height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public SphereMap(int mapWidth, int mapHeight) {
this(0x1337BABE1337D00DL, mapWidth, mapHeight, FastNoise.instance,1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a
* 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to
* have significantly-exaggerated-in-size features while the equator is not distorted.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public SphereMap(long initialSeed, int mapWidth, int mapHeight) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a
* 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to
* have significantly-exaggerated-in-size features while the equator is not distorted.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with the given octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public SphereMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, octaveMultiplier);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a
* 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to
* have significantly-exaggerated-in-size features while the equator is not distorted.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses the given noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
*/
public SphereMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) {
this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a
* 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to
* have significantly-exaggerated-in-size features while the equator is not distorted.
* Takes an initial seed, the width/height of the map, and parameters for noise
* generation (a {@link Noise3D} implementation, which is usually {@link FastNoise#instance}, and a
* multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers
* producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used,
* since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map
* cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for
* {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the
* seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance}
* because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should
* probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on
* generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps
* that don't require zooming.
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise#instance}
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public SphereMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier) {
super(initialSeed, mapWidth, mapHeight);
xPositions = new double[width][height];
yPositions = new double[width][height];
zPositions = new double[width][height];
terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq);
terrainLayered = new Noise.InverseLayered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325);
heat = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75);
moisture = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55);
otherRidged = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq);
}
@Override
public int wrapY(final int x, final int y) {
return Math.max(0, Math.min(y, height - 1));
}
/**
* Given a latitude and longitude in radians (the conventional way of describing points on a globe), this gets the
* (x,y) Coord on the map projection this generator uses that corresponds to the given lat-lon coordinates. If this
* generator does not represent a globe (if it is toroidal, for instance) or if there is no "good way" to calculate
* the projection for a given lat-lon coordinate, this returns null. This implementation never returns null.
* If this is a supported operation and the parameters are valid, this returns a Coord with x between 0 and
* {@link #width}, and y between 0 and {@link #height}, both exclusive. Automatically wraps the Coord's values using
* {@link #wrapX(int, int)} and {@link #wrapY(int, int)}.
* @param latitude the latitude, from {@code Math.PI * -0.5} to {@code Math.PI * 0.5}
* @param longitude the longitude, from {@code 0.0} to {@code Math.PI * 2.0}
* @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
*/
@Override
public Coord project(double latitude, double longitude) {
int x = (int)(((longitude - getCenterLongitude() + 12.566370614359172) % 6.283185307179586) * 0.15915494309189535 * width + 0.5),
y = (int)((NumberTools.sin(latitude) * 0.5 + 0.5) * height + 0.5);
return Coord.get(
wrapX(x, y),
wrapY(x, y));
}
protected void regenerate(int startX, int startY, int usedWidth, int usedHeight,
double landMod, double coolMod, int stateA, int stateB)
{
boolean fresh = false;
if(cacheA != stateA || cacheB != stateB || landMod != landModifier || coolMod != coolingModifier)
{
minHeight = Double.POSITIVE_INFINITY;
maxHeight = Double.NEGATIVE_INFINITY;
minHeat0 = Double.POSITIVE_INFINITY;
maxHeat0 = Double.NEGATIVE_INFINITY;
minHeat1 = Double.POSITIVE_INFINITY;
maxHeat1 = Double.NEGATIVE_INFINITY;
minHeat = Double.POSITIVE_INFINITY;
maxHeat = Double.NEGATIVE_INFINITY;
minWet0 = Double.POSITIVE_INFINITY;
maxWet0 = Double.NEGATIVE_INFINITY;
minWet = Double.POSITIVE_INFINITY;
maxWet = Double.NEGATIVE_INFINITY;
cacheA = stateA;
cacheB = stateB;
fresh = true;
}
rng.setState(stateA, stateB);
long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong();
int t;
landModifier = (landMod <= 0) ? rng.nextDouble(0.29) + 0.91 : landMod;
coolingModifier = (coolMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : coolMod;
double p,
ps, pc,
qs, qc,
h, temp,
i_w = 6.283185307179586 / width, i_h = 2.0 / (height+2.0),//(3.141592653589793) / (height+2.0),
xPos = startX, yPos, i_uw = usedWidth / (double)width, i_uh = usedHeight * i_h / (height+2.0);
final double[] trigTable = new double[width << 1];
for (int x = 0; x < width; x++, xPos += i_uw) {
p = xPos * i_w + centerLongitude;
trigTable[x<<1] = NumberTools.sin(p);
trigTable[x<<1|1] = NumberTools.cos(p);
}
yPos = startY * i_h + i_uh;
for (int y = 0; y < height; y++, yPos += i_uh) {
qs = -1 + yPos;//-1.5707963267948966 + yPos;
qc = NumberTools.cos(NumberTools.asin(qs));
//qs = qs;
//qs = NumberTools.sin(qs);
for (int x = 0, xt = 0; x < width; x++) {
ps = trigTable[xt++] * qc;//NumberTools.sin(p);
pc = trigTable[xt++] * qc;//NumberTools.cos(p);
xPositions[x][y] = pc;
yPositions[x][y] = ps;
zPositions[x][y] = qs;
heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(pc +
terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5,
ps, qs, seedA) + landModifier - 1.0);
heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps
+ otherRidged.getNoiseWithSeed(pc, ps, qs,seedB + seedC)
, qs, seedB));
moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs
+ otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA)
, seedC));
minHeightActual = Math.min(minHeightActual, h);
maxHeightActual = Math.max(maxHeightActual, h);
if(fresh) {
minHeight = Math.min(minHeight, h);
maxHeight = Math.max(maxHeight, h);
minHeat0 = Math.min(minHeat0, p);
maxHeat0 = Math.max(maxHeat0, p);
minWet0 = Math.min(minWet0, temp);
maxWet0 = Math.max(maxWet0, temp);
}
}
minHeightActual = Math.min(minHeightActual, minHeight);
maxHeightActual = Math.max(maxHeightActual, maxHeight);
}
double heatDiff = 0.8 / (maxHeat0 - minHeat0),
wetDiff = 1.0 / (maxWet0 - minWet0),
hMod;
yPos = startY * i_h + i_uh;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++, yPos += i_uh) {
temp = Math.abs(yPos - 1.0);
temp *= (2.4 - temp);
temp = 2.2 - temp;
for (int x = 0; x < width; x++) {
// heightData[x][y] = (h = (heightData[x][y] - minHeightActual) * heightDiff - 1.0);
// minHeightActual0 = Math.min(minHeightActual0, h);
// maxHeightActual0 = Math.max(maxHeightActual0, h);
h = heightData[x][y];
heightCodeData[x][y] = (t = codeHeight(h));
hMod = 1.0;
switch (t) {
case 0:
case 1:
case 2:
case 3:
h = 0.4;
hMod = 0.2;
break;
case 6:
h = -0.1 * (h - forestLower - 0.08);
break;
case 7:
h *= -0.25;
break;
case 8:
h *= -0.4;
break;
default:
h *= 0.05;
}
heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp);
if (fresh) {
ps = Math.min(ps, h); //minHeat0
pc = Math.max(pc, h); //maxHeat0
}
}
}
if(fresh)
{
minHeat1 = ps;
maxHeat1 = pc;
}
heatDiff = coolingModifier / (maxHeat1 - minHeat1);
qs = Double.POSITIVE_INFINITY;
qc = Double.NEGATIVE_INFINITY;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff));
moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff);
if (fresh) {
qs = Math.min(qs, h);
qc = Math.max(qc, h);
ps = Math.min(ps, temp);
pc = Math.max(pc, temp);
}
}
}
if(fresh)
{
minHeat = qs;
maxHeat = qc;
minWet = ps;
maxWet = pc;
}
landData.refill(heightCodeData, 4, 999);
/*
if(generateRivers) {
if (fresh) {
addRivers();
riverData.connect8way().thin().thin();
lakeData.connect8way().thin();
partialRiverData.remake(riverData);
partialLakeData.remake(lakeData);
} else {
partialRiverData.remake(riverData);
partialLakeData.remake(lakeData);
int stx = Math.min(Math.max((zoomStartX >> zoom) - (width >> 2), 0), width),
sty = Math.min(Math.max((zoomStartY >> zoom) - (height >> 2), 0), height);
for (int i = 1; i <= zoom; i++) {
int stx2 = (startCacheX.get(i) - startCacheX.get(i - 1)) << (i - 1),
sty2 = (startCacheY.get(i) - startCacheY.get(i - 1)) << (i - 1);
//(zoomStartX >> zoom) - (width >> 1 + zoom), (zoomStartY >> zoom) - (height >> 1 + zoom)
// Map is 200x100, GreasedRegions have that size too.
// Zoom 0 only allows 100,50 as the center, 0,0 as the corner
// 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
// 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
System.out.printf("zoomStartX: %d zoomStartY: %d, stx: %d sty: %d, stx2: %d, sty2: %d\n", zoomStartX, zoomStartY, stx, sty, stx2, sty2);
if ((i & 3) == 3) {
partialRiverData.zoom(stx, sty).connect8way();
partialRiverData.or(workingData.remake(partialRiverData).fringe().quasiRandomRegion(0.4));
partialLakeData.zoom(stx, sty).connect8way();
partialLakeData.or(workingData.remake(partialLakeData).fringe().quasiRandomRegion(0.55));
} else {
partialRiverData.zoom(stx, sty).connect8way().thin();
partialRiverData.or(workingData.remake(partialRiverData).fringe().quasiRandomRegion(0.5));
partialLakeData.zoom(stx, sty).connect8way().thin();
partialLakeData.or(workingData.remake(partialLakeData).fringe().quasiRandomRegion(0.7));
}
//stx = (width >> 1) ;//Math.min(Math.max(, 0), width);
//sty = (height >> 1);//Math.min(Math.max(, 0), height);
}
System.out.println();
}
}
*/
}
}
/**
* A concrete implementation of {@link WorldMapGenerator} that projects the world map onto an ellipse that should be
* twice as wide as it is tall (although you can stretch it by width and height that don't have that ratio).
* This uses the Mollweide projection.
* Example map, showing ellipse shape
*/
@Beta
public static class EllipticalMap extends WorldMapGenerator {
// protected static final double terrainFreq = 1.35, terrainRidgedFreq = 1.8, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375, riverRidgedFreq = 21.7;
protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375;
protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY,
minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY,
minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY;
public final Noise3D terrain, heat, moisture, otherRidged, terrainLayered;
public final double[][] xPositions,
yPositions,
zPositions;
protected final int[] edges;
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Always makes a 200x100 map.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
* If you were using {@link EllipticalMap#EllipticalMap(long, int, int, Noise3D, double)}, then this would be the
* same as passing the parameters {@code 0x1337BABE1337D00DL, 200, 100, FastNoise.instance, 1.0}.
*/
public EllipticalMap() {
this(0x1337BABE1337D00DL, 200, 100, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes only the width/height of the map. The initial seed is set to the same large long
* every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and
* height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public EllipticalMap(int mapWidth, int mapHeight) {
this(0x1337BABE1337D00DL, mapWidth, mapHeight, FastNoise.instance,1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public EllipticalMap(long initialSeed, int mapWidth, int mapHeight) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with the given octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public EllipticalMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, octaveMultiplier);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. The suggested Noise3D
* implementation to use is {@link FastNoise#instance}.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
*/
public EllipticalMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) {
this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes an initial seed, the width/height of the map, and parameters for noise generation (a
* {@link Noise3D} implementation, where {@link FastNoise#instance} is suggested, and a
* multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers
* producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used,
* since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map
* cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for
* {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the
* seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance}
* because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should
* probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on
* generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps
* that don't require zooming.
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public EllipticalMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier) {
super(initialSeed, mapWidth, mapHeight);
xPositions = new double[width][height];
yPositions = new double[width][height];
zPositions = new double[width][height];
edges = new int[height << 1];
terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq);
terrainLayered = new Noise.InverseLayered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325);
heat = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75);
moisture = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55);
otherRidged = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq);
}
@Override
public int wrapX(final int x, int y) {
y = Math.max(0, Math.min(y, height - 1));
if(x < edges[y << 1])
return edges[y << 1 | 1];
else if(x > edges[y << 1 | 1])
return edges[y << 1];
else return x;
}
@Override
public int wrapY(final int x, final int y) {
return Math.max(0, Math.min(y, height - 1));
}
protected void regenerate(int startX, int startY, int usedWidth, int usedHeight,
double landMod, double coolMod, int stateA, int stateB)
{
boolean fresh = false;
if(cacheA != stateA || cacheB != stateB || landMod != landModifier || coolMod != coolingModifier)
{
minHeight = Double.POSITIVE_INFINITY;
maxHeight = Double.NEGATIVE_INFINITY;
minHeat0 = Double.POSITIVE_INFINITY;
maxHeat0 = Double.NEGATIVE_INFINITY;
minHeat1 = Double.POSITIVE_INFINITY;
maxHeat1 = Double.NEGATIVE_INFINITY;
minHeat = Double.POSITIVE_INFINITY;
maxHeat = Double.NEGATIVE_INFINITY;
minWet0 = Double.POSITIVE_INFINITY;
maxWet0 = Double.NEGATIVE_INFINITY;
minWet = Double.POSITIVE_INFINITY;
maxWet = Double.NEGATIVE_INFINITY;
cacheA = stateA;
cacheB = stateB;
fresh = true;
}
rng.setState(stateA, stateB);
long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong();
int t;
landModifier = (landMod <= 0) ? rng.nextDouble(0.2) + 0.91 : landMod;
coolingModifier = (coolMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : coolMod;
double p,
ps, pc,
qs, qc,
h, temp, yPos, xPos,
i_uw = usedWidth / (double)width,
i_uh = usedHeight / (double)height,
th, thx, thy, lon, lat, ipi = 1.0 / Math.PI,
rx = width * 0.25, irx = 1.0 / rx, hw = width * 0.5,
ry = height * 0.5, iry = 1.0 / ry;
yPos = startY - ry;
for (int y = 0; y < height; y++, yPos += i_uh) {
thx = NumberTools.asin((yPos) * iry);
lon = (thx == Math.PI * 0.5 || thx == Math.PI * -0.5) ? thx : Math.PI * irx * 0.5 / NumberTools.cos(thx);
thy = thx * 2.0;
lat = NumberTools.asin((thy + NumberTools.sin(thy)) * ipi);
qc = NumberTools.cos(lat);
qs = NumberTools.sin(lat);
boolean inSpace = true;
xPos = startX;
for (int x = 0; x < width; x++, xPos += i_uw) {
th = lon * (xPos - hw);
if(th < -3.141592653589793 || th > 3.141592653589793) {
heightCodeData[x][y] = 10000;
inSpace = true;
continue;
}
if(inSpace)
{
inSpace = false;
edges[y << 1] = x;
}
edges[y << 1 | 1] = x;
th += centerLongitude;
ps = NumberTools.sin(th) * qc;
pc = NumberTools.cos(th) * qc;
xPositions[x][y] = pc;
yPositions[x][y] = ps;
zPositions[x][y] = qs;
heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(pc +
terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5,
ps, qs, seedA) + landModifier - 1.0);
heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps
+ otherRidged.getNoiseWithSeed(pc, ps, qs,seedB + seedC)
, qs, seedB));
moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs
+ otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA)
, seedC));
minHeightActual = Math.min(minHeightActual, h);
maxHeightActual = Math.max(maxHeightActual, h);
if(fresh) {
minHeight = Math.min(minHeight, h);
maxHeight = Math.max(maxHeight, h);
minHeat0 = Math.min(minHeat0, p);
maxHeat0 = Math.max(maxHeat0, p);
minWet0 = Math.min(minWet0, temp);
maxWet0 = Math.max(maxWet0, temp);
}
}
minHeightActual = Math.min(minHeightActual, minHeight);
maxHeightActual = Math.max(maxHeightActual, maxHeight);
}
double heatDiff = 0.8 / (maxHeat0 - minHeat0),
wetDiff = 1.0 / (maxWet0 - minWet0),
hMod,
halfHeight = (height - 1) * 0.5, i_half = 1.0 / halfHeight;
yPos = startY + i_uh;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++, yPos += i_uh) {
temp = Math.abs(yPos - halfHeight) * i_half;
temp *= (2.4 - temp);
temp = 2.2 - temp;
for (int x = 0; x < width; x++) {
h = heightData[x][y];
if(heightCodeData[x][y] == 10000) {
heightCodeData[x][y] = 1000;
continue;
}
else
heightCodeData[x][y] = (t = codeHeight(h));
hMod = 1.0;
switch (t) {
case 0:
case 1:
case 2:
case 3:
h = 0.4;
hMod = 0.2;
break;
case 6:
h = -0.1 * (h - forestLower - 0.08);
break;
case 7:
h *= -0.25;
break;
case 8:
h *= -0.4;
break;
default:
h *= 0.05;
}
heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp);
if (fresh) {
ps = Math.min(ps, h); //minHeat0
pc = Math.max(pc, h); //maxHeat0
}
}
}
if(fresh)
{
minHeat1 = ps;
maxHeat1 = pc;
}
heatDiff = coolingModifier / (maxHeat1 - minHeat1);
qs = Double.POSITIVE_INFINITY;
qc = Double.NEGATIVE_INFINITY;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff));
moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff);
if (fresh) {
qs = Math.min(qs, h);
qc = Math.max(qc, h);
ps = Math.min(ps, temp);
pc = Math.max(pc, temp);
}
}
}
if(fresh)
{
minHeat = qs;
maxHeat = qc;
minWet = ps;
maxWet = pc;
}
landData.refill(heightCodeData, 4, 999);
}
}
/**
* An unusual map generator that imitates an existing map (such as a map of Earth, which it can do by default). It
* uses the Mollweide projection (an elliptical map projection, the same as what EllipticalMap uses) for both its
* input and output; an example can be seen here,
* imitating Earth using a 512x256 world map as a GreasedRegion for input.
*/
public static class MimicMap extends EllipticalMap
{
public GreasedRegion earth;
public GreasedRegion shallow;
public GreasedRegion coast;
public GreasedRegion earthOriginal;
/**
* Constructs a concrete WorldMapGenerator for a map that should look like Earth using an elliptical projection
* (specifically, a Mollweide projection).
* Always makes a 512x256 map.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
* If you were using {@link MimicMap#MimicMap(long, Noise3D, double)}, then this would be the
* same as passing the parameters {@code 0x1337BABE1337D00DL, FastNoise.instance, 1.0}.
*/
public MimicMap() {
this(0x1337BABE1337D00DL
, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the
* given GreasedRegion's "on" cells, using an elliptical projection (specifically, a Mollweide projection).
* The initial seed is set to the same large long every time, and it's likely that you would set the seed when
* you call {@link #generate(long)}. The width and height of the map cannot be changed after the fact.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied
*/
public MimicMap(GreasedRegion toMimic) {
this(0x1337BABE1337D00DL, toMimic, FastNoise.instance,1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the
* given GreasedRegion's "on" cells, using an elliptical projection (specifically, a Mollweide projection).
* Takes an initial seed and the GreasedRegion containing land positions. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied
*/
public MimicMap(long initialSeed, GreasedRegion toMimic) {
this(initialSeed, toMimic, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the
* given GreasedRegion's "on" cells, using an elliptical projection (specifically, a Mollweide projection).
* Takes an initial seed, the GreasedRegion containing land positions, and a multiplier that affects the level
* of detail by increasing or decreasing the number of octaves of noise used. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact.
* Uses FastNoise as its noise generator, with the given octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public MimicMap(long initialSeed, GreasedRegion toMimic, double octaveMultiplier) {
this(initialSeed, toMimic, FastNoise.instance, octaveMultiplier);
}
/**
* Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the
* given GreasedRegion's "on" cells, using an elliptical projection (specifically, a Mollweide projection).
* Takes an initial seed, the GreasedRegion containing land positions, and parameters for noise generation (a
* {@link Noise3D} implementation, which is usually {@link FastNoise#instance}. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call
* {@link #generate(long)}. The width and height of the map cannot be changed after the fact. Both FastNoise
* and FastNoise make sense to use for {@code noiseGenerator}, and the seed it's constructed with doesn't matter
* because this will change the seed several times at different scales of noise (it's fine to use the static
* {@link FastNoise#instance} or {@link FastNoise#instance} because they have no changing state between runs
* of the program). Uses the given noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} or {@link FastNoise}
*/
public MimicMap(long initialSeed, GreasedRegion toMimic, Noise3D noiseGenerator) {
this(initialSeed, toMimic, noiseGenerator, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the
* given GreasedRegion's "on" cells, using an elliptical projection (specifically, a Mollweide projection).
* Takes an initial seed, the GreasedRegion containing land positions, parameters for noise generation (a
* {@link Noise3D} implementation, which is usually {@link FastNoise#instance}, and a multiplier on how many
* octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers producing even more
* detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used,
* since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map
* cannot be changed after the fact. FastNoise will be the fastest 3D generator to use for
* {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the
* seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance}
* because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should
* probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on
* generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps
* that don't require zooming.
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} or {@link FastNoise}
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public MimicMap(long initialSeed, GreasedRegion toMimic, Noise3D noiseGenerator, double octaveMultiplier) {
super(initialSeed, toMimic.width, toMimic.height, noiseGenerator, octaveMultiplier);
earth = toMimic;
earthOriginal = earth.copy();
coast = earth.copy().not().fringe(2);
shallow = earth.copy().fringe(2);
}
/**
* Constructs a 512x256 elliptical world map that will use land forms with a similar shape to Earth.
* @param initialSeed
* @param noiseGenerator
* @param octaveMultiplier
*/
public MimicMap(long initialSeed, Noise3D noiseGenerator, double octaveMultiplier)
{
this(initialSeed,
GreasedRegion.deserializeFromString(LZSPlus.decompress(
"ᖢᠳ\u0088氦悠ぉ䓙㛃疓ᾏ尿䣢✶㒫敹✊盰织\u0CDC淶\u1FD5玿帛悟სᇢ捉䀡㇀׀ԃ̠\u0CE4倽䘾६.ど嗋䇗掊䞯\u0AC6ᖌ猘⎲䯬䃣䱟Ȥᮔ囵⟡㎡ᚲᠫዌђ\u2D26ڐ䍘෴℞吹ᡖ孶擉ྠ䒻㒵၊恝Ψᙤॉᄕ̡ᖕ᪁敨ᖨᝆӋᝳ娻Žዠす\u17DE∰屴啎▋慳 ᝐ⫑卲ฤ〸禷\u0DEB㕀ᕅ祩噛❆寰嫩旭⪾൵◬أ椩ழ䧩Ⱖ䥨ᥞ暯◉⅘咖᰷♙撹㿦暠䂗㠺̒ᚧⱖస岾㭹ኙಒ\u2EF5༪ູᡵ旈簡ఠ㐈恝〺㈈ਮཱུ╅ⱖ㚼嘐琫\u2E42\u0BA0瓷\u2BF2暅㺨ಬᔑ\u2E61\u1AFA◺吮\u31E6埰⛿ⱇ坒Łт喩唒偉ⶳ㒗 ⎊̀ᐮ⩕滹\u2BF3༘ᰰ睹侄\u2D2BⒺ濙洡⧅咫ᤪЯ░ଌ浨槓哬呾Ê䴎柖梑䩊㥤Ꮻ猑嗀挗Ꮹ㐠愢⼐ʫᤔ獩緔ⷰᒺⱛ\u2BF2ⲑ➱\u09D0䯷㽶ឍ㐴敻䭊揚▪ɍ滔氣ြ㶡ᰱ榓哥磀ኲ勆ᴕ总嗀ජ佋⒮宵挈ᙛ瑻⩔伉と崡擇异Ü榉᪪⛓珛盀皅旱㝙♵⊼捝削℄漩⍨牎ᅬⵚ䈬ᒢ䓉㒩㉑坌\u08D1ဤ爅㩈թĨ傠㈾ᓣㅊ曰༱◉十䐢䑩ྣੈӑ⇛ᅅٌ䛄㔼ѱ᥎晘☤埰瞱㓩強૰䑢䉌圧䜹挥⚨ؤұ⢘䫂瀯愨恩朩\u1AB8ᐩྱ㙴㈤䳲ḧ冐仔ඡパ搈⌢斅⊹䩃ģ䎘㩔\u0878犵໐౩䶒㉏䋰ᑦ៩义⠉ᄤ㔵祅䕑䒊䨝㲀䰹縦䖑ऩ侪㨱ଣ稲勅ᒕ䓈盆䜙㦂⼝⾴䃓フ䔸ᕘ杳山༴礷䐹\u2E5A⢡䵋⟕♦促َ\u13FD䘷䎘㫄₊➫为㈥琥ୌɔ瘴DZಿ⌘䍻ⱺ睊传〣䟨矗\u0B7A捪圹⒞楙ॆ≝䜷㈝ᕚ\u243B䁣爙䭡䙐犥᠐炎凙澇皱㝬敤ᓔĽվ♛濞\u2D26䑾嚣ᤲ冐ヵ勱穗愛嫞殥沱䝩僫攒ம梃့涔愥䝰卪ᏘᒎᏨ坼\u0EC7ệ䑨⾭ⓕ断冐秝\u0EDB炑\u0C51崣䖭槉⃝\u1AC3䑱㝛\u19DB⪷䂙榄〓撔࿘幐\u0C3A哨歵ᩎ䄩ٛ₵孚⥌ằ㎇⋁強瓇䂨ᕎ斨漖\u2B68ᘱ䔭档崷哧⢑ᴺ⏩唒偱䩋瓰㨾䙛穄䬵‰攮⾤㤸ဴ㥱ᵈ⾮ᕓᝓ⎔\u20C3\u31BB归⍉⛝义㴑绔DZ䌳庳◥Ⓢᗻ⊡\u0E5D䒐◔≄ዂ⸮ㇸ㪏╘˷絔䮙ス烰䬱फ粻塐༸ᵉ倠ᱨ̹瘓⾌儩璒ⷵ㳭╩瑷㙰⊖线咆❰悆䡊狋䠋咥㷫Ꮾ绹䙆❨估\u0BD2ⅵ㾋䛨堡㻟౩漽棆\u05C8材⎄≠䉖Ᏸ瀳ჸ✑窿屍ᢪ戬獠ᩇ㱛ͨ㮥\u0089\u0B80ⴹ∔ہؤ㮋㐶摝㘌\u0C00竳\u0C91在瓉\u05CA㩀⎬㯧㱃≈ബ䛔䔢ᒧ禃Ḁ塻懐噳手ቌࠣ懞ⶐ愯ᇎ\u0BE0䑿捡㢫⡩\u08CE弡Ӯ\u0B11㬼ハᏙ☬拎ᚣѵᄔⴶᄫ߃Ṣ紬㲁\u05CE搔ᷤ㲟ӑ⍯䉶૭璡抚ᨤ⑫䜀䑓\u1779䍞䂥౧ༀมᷙ埣㶨┈厭砨䪪率绱⪂䘢意\u0FE6晑剚⍢䥹ᜒ䛪勾↜唿ࡂᦱ䈺\u09D3㊩抋▤㚬ሒղਦ剫ၒ灣▮፮㑜⛼ኲ㤆ଠႤ灃䮂ᴽ⋜᬴椁仪冻೭ៀ樣テ⳹洭㧥㚰广Ԑ瞡哵♥䱤為チ昽ガ৮礞瘌焈帺e⼳ℎ᮱Ꮨ碌淪洊\u2432㎋ࣽᑰẪ⃘䃑Ῑ٘᪡碴⟌瘍㧪\u0C0Dද၊ጀ个䭼㺦ṵ嗹檋そ溁剫℆㏒⊃䅊⠭峏ង刴幦䋜毹ᇱტ㜳䩴₲๎狹ᑪ䈯ಐ䳚恹㡲ᙏ穠∢咏痄й嬓䆵㓚爉㉀帽㫪爓䨮擐沭Ⴈ༢替倱㉁翕䋸ᵉ㑍▬⺨㻱ȁ\u085F籗Ủ၊炄ᎆ\u312Fδஈאד昼愹ପ\u10C6\u05FBˊ䣤ٌů榥⡒䛗Ꮗଛ⤤欽ᤰ⊈Ꮸ⏨ᥒ勅≔᎔⭆帺ᗈ摎剬䂎瓽䔀䂀བྷ⁍ᙴ䬥䉄⋇ᭉ⠵⊣熵奫懪⊸唳ൌ㉢∔⁙ㅌ\u0588儆˅༠焚Ϫ椵䁓㲁䉛䁰嵲ゕᢠ䭚ⵠ۶杌痸仭籅檰㗠ⴹᖱᵰ橴倳\u0C50∎䦀ẫᖬ揸嶕徃㱲僴͑䉓ቖ渌౽檕㒣ᵏ彛Ⴛၟ㓢ᘡҸኵᮉ滽㯼͍ਧⓖ攵ᥠ尿㗠⧴展⡡㝲ᰬᣰ䔭呐篗䵮ຽ༴涙ᔽ⻣㈾ใ竓子柔棠懠⻡䵵啠惭梁䪍ᷕႆⅣ㓚䌽∧略䕲\u242F摥䪈Ἤھḁ㢨㣈ᨉశ䰤愻ᖊ塢₵➖ᜄⳇⰄュ౬䑘淴卽\u1AC6̲㓚ې瑚㚬㊤棆椔粭巓儑冣烒峰嘲癜₂ᬅվ啕ピ䴼ḩ樿ᩓ\u00AD県る⺟ᯍ幄┏敃䄸疦\u1DF4⣜୷⨎旄\u4DB9㠬㒐ડ▋ៃ䋌屈်娦杻எᅿ続㐹町\u0B8C椡嫾㦾峡㎁憈ᜮ␖\u0AF7妈㛁ⷮ徬ᣴ◧瑳\u0560㛞灂唊㴨͠䵒ᵎ惵䃾断姰ⷘᠯ䙬淫㩵ݴ㴾ⅱ㨔希⢫岄ŏ摉掜Η垫穆π墉奪毤ᖪऐ敖墷ॴ䧯寞⁀榧ᶑඬᬮ嵊ȸᖤ䵠庯ਿ浬姮᱖䀱හ\u18F8燱ὔ㕴䵣垏፭\u12B6屜洴皽禴砒粴˲\u18AC᧹ᴔ՝紊姳䡾滄痱崇狉物᪦唫埩珑㯣ᚸ剗姯䌉侙窼◴䆴⟑❿僥缾ؑཝᬒ僝䝾炾巤ᶏ瘃䮁㮧䜷ᡯᵴ崞狹ᚍ\u10CC巗湅ᵼ䟪䷴䫅粲╥଼ٕ㗪⺉⥏㴣喂婼剗\u2B92剻㫇捽▵\u0C75ᢁ玈弭ᠺ焍\u0B4E竽盺ه廜㟘䫠\u2438ᔨ揤➿国溫扜䯴㻽凃摟Ⓒ枴㴇ၮ祡ㆠ䢤㍮ၐ檌ຄ䒇ᦒ枝\u2B68ⲟ孷⭖㱾䁙傛甈ٯ嫣唎ᮩቲ̫⧧璒⌧力ߚ䮸卣妰\u009F∞ړ絅䊓䠖籓簁䆗紓㜏撦㠈尔⎈Ṑ㟩湟㢛ሸ௭祫ᾓ嫭員㬠ᶉ᥈䮼⬁፼∆䕁‶ౘ玉厴口∣ɹ圐⬥圶愛ᜩ༨㭄Ӽ䰣⠿ㅑ⎉ɀ䳄殂≠ϴ\u08C5疄⩜Ĺ䑯ⅽ㉤ᦥ嘃ᦋ⇬ް㨨㠦Ԝ䤴᱈溏䂀值㍮ᡮ㎕\u0E71㼤\u086B䌬\u0863\u08C1夿⩈倥恢ů媉ᑵ㴯ᄃ匉ᜣ䜍圢ڦᏘᨼį妠䝣嵫\u0B49⫢ᇠ丌ㄜԠ⿸♣⩋\u23FD䴙८ѝ㊈ᤨ狡Üޛᗢ桢㈩⣏ዡ槈婁慸Ꭳ❯㵀䝘⑃㬏癃犤Ꮳ㪎㹥凜щ測⠭Ɉٱ䄺㝗ޜɃᔪ庈䟸ℵ䝪䢗獶䓃☶ऒ䛧ᖵᔵ\u08D0ㆳḀண捆䚤⣱愶棙ѕ\u0D98䑦Ѵ㈔ၐ箠椴䖻䵃䅬ᣨ䖬㢩Զ呝㊡䲝⌇祡側㒃儰䰪愫᩠嬼杒䫔㑈⌵⤉Ǖ弴ᝯ䝂挨唃⬆ሦ㥌⏂挷ᕺ拉\u0DFD紨礉氟䯜᪪惝䝼⡐劫䉵ገ\u10C9㓄礻㪗弽䂹⨴炧ẙ槦Ⅎ㊶᠂嬫䡘⓬ij県犖僂⒙梳ᣔ扢卑䬠Ү移寝ⰼṔ珜岳䄥ፃ卒ὡာ磑㤍ᆲ梼操㧸ᭁ甩䅮掲ⓢ̰壞✄Ḅ嬸畦抖ᥢ䰱慿⭂⻙Ⓖ擂䔕Ӑᑣ磏î̳ỂႿ⬫ᥙ檴攁䍅ᖁ缀刳̢ͼ䪼㔚戭ᩳ⏢╭㏌ᝨ㿈呰Ꮲ塣⨼瓈̲㳳粴渥⡳䞸猩ֆ㈼Ձ渤汙䌢\u1C4Cⓢⱪ猦㕙↴ヹ䚻宬ڻ䅮熺⍒㊼ᢃ⤛̀㯨᜴䋤ԑֺ䔂擌ܙ儠唰䍔൰崽ぽɈᵀᒨҔ时ⷳ挱ぷ槸ὀ澭Ʉ䩅ሀ⌨摯\u0AF4\u08C0綱哆犲䢀ᘤ㑙晤ᡅ桉㖌捍ὠ泫⳥杳ᓌ慯梃僇ྐ\u0EEDٷⓞᓀޱ㔓箅叁勡喔牾㫠ስ䅜⋾㬓潁⡛ةဉ濧ޜ厠䛔㫨慮݈Ɠ瞶ぎ䤽॓ḧ嵄爁ń囬奢簎⠒倈泤䍫ਬ䡄䞔\u087Aᓒ峆㕊\u0866᧘◪癡㬾⑀痫潂⡧᪀㊣䌆摡㥴䛢溊ᓤᜂᨧ亀䀷ᤪ溭渵昗Č哋㳵↑ⳙᖸ癭犖呪ᓂ灠⎎ᜉ狭佣㣄ह⬲喂५౫ᓂ勩\u1737\u05F8㉳ࡍᒡ\u0DE9ᛂጝᕉ≫ᱵ⋓ᕟ᭪厵磆昁⥴↠狳ᗗᳫ六睵䖟ᩨ汹犮ቱ⟫无勻ក䧌ܺ˶ᒙ\u2D78并助梉\u3101㱶棝â᠌\u2073挤噊䥊慾ᴨ⇅㫉Χ䴙ᗙ㜪ռ\u0884唎䈔ঈ⌁ၴ䕀糥⪰嘵㿠彤̦啚䰹✼で䌹崥止⋳Ⱈ㲫嘭欛歲ᇋ毮ᑂ䦧Ῐ䢡ℑ戺ㆢ人唉囁俰⋮硊䏓丁㸎礿䋘ᶋ纽畠枍㟋㭼∥坓\u1CA5丩㥱⌄\u23F9䤺⢶䝽㚊絶牛ਐ䡤殹╭ᇍ㮲罅欈䊱彁ᎄ\u0E7A⟘ᜱ㻭䣕嘢ㄲ㴿䨰筬\u0A3B撍♓䂴ტ歬ぉઽ㿨㘎䋵䯔͇ᢩ嵶ᬮᓩ巩偡⏈䨀䷪窫磏䤜ち✆᭘ߨ㓳䜟响啄俌䙓吮䶔⠹沼㊓⼉峿\u0893ႸӠԂ梓℃㫉滦ˁ祐䨪勵⚾㗫ⵕ¤嚮懸䬄\u1FB5䍏柱ኺⲋ嚯䭋䊼Ӻᢶ\u08B2⋳┦欺ᩌ\u1C81㾀溚ᇔඅ┥䊷㞅䷻⸉盔\u09D1͘剸狿᠘ᗻᘮಘᯣႪ佻盧掘啋૮㠸甛⊈⠅䦖兙ᅉ⨍湓¼ᘀ癍ौ爧㙛廭沢⦣ૈ⁼灳䋤წⳡ䐪睧ૠ姿\u0EFA疈䴥㋨Ὠ⃡ፚ燳\u2EFC痦⊢嗩\u243E䚜ᷛᘋܫÐ㮩叁⚭㦪ࠢ俇\u1CB5ᅯ⡍函䮂䈞⁝⚄拵盱\u1C4C乧㱽焙帹㏾欣痎⤚\u16FA弐ϓ匀ⶢሬ䛝ਫ▉娴嬑候啯㫡䉝㖀㡌绎倠ⵀ兩㢲悌ᰛ\u1AFD累ኡ怌叹紑䇳䋻ห禌反Ꮣ攥䖈✖⸧ၰ䈟瑻㽻㢢҃䇵˙⪮䖘᩹Ⴇᦀ縦妈ዌ棲悥䐟ᙱ㑀瘱䃇Ń矡䚸寵䕡ඈ⚧Ű䯕ᄠ塵卟㗤活㡧˥䌚≻⡹歖ᵀ帣炨⊠ᷘ曩倣⨆㓀ⳍⅿ扚䅪橐䮆䊈抣㪾䒇瞍Ꮵ⤦Ṟ\u0FF1⠔᱄䞅晸斬ᩊ䡯棹㟘癶䚈樧㺚䶮㙰䭗㧹杂楥⛈汈㹓㡍\u0E5F㾐䱘噱໐抌⧴™સ孱䮵Ҷ䃩ṱ⩠皆ᑉ⧭㡷ᒇ΄Ⴆ⢎&\u2BE1ః≧ぃ̲᳀俾⨆亠东ଯ⠠㈬䈨甠\u0D54⤒ᯘ浟硩㮯㸼劬䉳⡔\u0C04㪫田≹֪叢槰揔檇㕃抒॥䛰汫砨愲\u1CACᔢ殆䲵㎇ፚ廐痝⸨㏡ʋ扚ሑⵋ咙ᯠ䌴咷\u088A㤜\u087Bᙌ్\u0CD4䳰偗ᡈ䘼Ї㡥秠䥇ކ⩦砺䎘Ⲉ偁媀檍چ枹בᮜ楙㝒ͳ䈬១唬吨ⱝ䮁僙剸ⶤɉ⺫౩扆䴓漥煼↮ᴧ㽕ᅥᨪૠ愤䀯⦡ა繛㉁䫼掷⚬ᐾ⸌綕泇㓢㩍ሃ厼\u2437↲\u08CDᢴ犉᭪潉㋝ᢶ矂ỹڳ幮䞸᭔\u1ADB㦩䖊\u0A31䫘偩ⷊ棶㸷ᘎ䔏㱶⫐㘕\u2EFA灐枩䱍\u23FAា囚⦻⾛㛁\u206Aಞ䄌䤠ⲻ䡸ͨ悻晣皹⎵㒈瞭喃㥢㓹ᇔᜂღ懜勯㑐淄ɖ嶶䷄眷䭗⇙的渆汭Ӹⷬ求䓗䜄玅塜媳给兢瑵ᄠ㱄孉竒㏭毡䷶濺ޤط桤敘ᄘ抯伊淖昢\u2B5A硳朗ۆ称䩧掦వ淙祁淮璖煡ᶫ㡄ఁᙉ〰护ୠ狞᷅汫䗉ࠠ㸅⬾洛ௗ䨫潊ዥ咣樳䋄勓痻捺㧭༲\u2FD6嵠䂸ЖᏒ\u1B4D叮㙴嬡ڵ䥣Ì䇡ḓ券䎁悞灨ṹᅠ゜淸泝ᠹ⇑ΰ竁攜握䷠㍴済㢜䏜ᱱമቑ䏬ㅿ✹凃᥀校᪐杓⏆㈰䷚ಔᔓ͑稳犎䶠渕ႃ᳖ᗎẊេ挩ē៷\u3103˭瑎᳁̺硒❓清۵勽⛏䌶ᅯ㲒吚矵㰊¤䁿䍠ৡफ籮䏾戢♺湗㆜Ӯ⡥⠫ᴔ廛₵छ⁙泯湫澍济瓺䄉ᐃᷥ凯暟⺙凕ᵺ᰿噀已௯些玾筀ℌደ䙯Ῐᑎ㺕㏈Ņ皍⦜\u0BD8嶥歏ᆑ̈忚哮娀噎䊒⢩\u2E6A暡䭡ᥰ媲ࡂ䫲羥ᩪ犇竷䌪廖籣傝嫇磪爣涂嘀ᒯ䞓ޅ罪㇠硺\u2BA5籽䓮摴䊹擠㔡批⧈⓹籡偹ᘧ\u19DB冦緊ෛ䭇殔ᔽ䋴ᮏ晸㘺䔴䠕❣桄ˀ倔䊨䖜㢫θ寺碉眂䃭仫㐖瀧ḏ៷ښὋ\u09C6´䏌⌍搯㙆ϰ㿰⬽孡䉆䴣仔\u08E0߲ᆞ皈椘䙫ᝣ甥ѡ㊧握昦瑨⟏掣璯ᄘࠐጣ笾睾篳ⸯ㒿ۢᘇ尳烹㊧呖歓樿妧ᐁㄡह㊛䱊愃我ᴛ⢝⇄みᙶ䌥㙱୰ིࠊ懊\u08D0峓承ʼnẹ睤ᰤⶴͥ瓿㹰棈伉⡪ቨ䶂ᖑ烒⡕Ḏᖙ⎼\u086Dཱྀ亾恝㠗\u08CE囕厦ཇ噇䞐掙㡬厄\u23FAア⒘㙋瑾婿砉䈗ᅈఎⱵ䵊\u0A61೫䘹恐䊩ᘈ䚐排ᭀ༧⩅ଃ\u175F䖡᭢ᙝ湄峐䩓呭敁ӗ⡰协ㅡ丣煛\u1FDCዃ喺惇ਭ㠩㸌䘳勸ܓ▢倿㫵⨁窺{\u0AE5ὈŔ⡥䄋с㺎ᢪጽ֢㎚㚊₮Χょኗ㓶ө㑰⒒箠๐⍯尔\u2BE5ᕤ̯䴭∘橡ጐ䄦峊ᔨ反әᭃḸ廪㭚桖婀㏂测搀⧡㏑洡㺚慡㓘ٰ晡Մ☴₇䈤̤㖑怢⣱\u0885儸▀夒倳榯\u2451ᐨ≲\u0096‑䴡⣫偉ᤌ\u20BBफ़懡糣燀Ź獪\u0D5AŰ煀\u08B2倣⃜ど姐潀戢ধ㨦ě࿚㒭䔲䆰房䤫 䜘ඹ媋氢盵䌛䒈྄㗢⢣\u0D81ᄎ旄ੜ䛪傭᱘汞㉀℁Υ淡爰䖅攍Ꮀ寄眡पᦿ瀣ψ\u0EE2ͱⰷ\u18AF甴↝凞\u0EF2cĤⅇ㡵⤘ณ\u0B00㿗\u0B3Aሯ¬⌠⫪☙䭍䌢䣶䂸႖㗡ŀǪՀဠᕓŸ⡃巍捝ङ⥍䌈ଥℬ㈤䈳儁\u0094ᛊ老ᛣ㿔⢉妭⚧ō䇃ᴨၓㅱĮᝂ篙Ϭㅞ\u202A祢䞈\u1DF9祣穥℧典㞖ᥢ䪫⒧⑇䇎毅՞㼱◰㬩兘䔉搿奢ᱠ\u052B◌ṩእચ←㛶䮺〣哚䝚䵤ⳁ嬁ᮤ䘰䈔掶ᄐḁ憣\u0B98ᤎヹ᎔垀昦⏉ᔁӔ䦤⺙ृ庱⒖४㍘㦨\u0BA7唯ਢ䱂⦧慾㚙ㆵ㍽ጴႲᄹք枃㈻ᔵ缉碸\u08D0ހ縩簠媂墜䜖Ụ煩⨳ⰳ㋼夵䢪˕籠稣ൽϒ吉屘禁⫥朿\u0E8B叿ೈ⦑Զ䤡䈭ᆷ唓䏠筃满坛\u244Fޑ䑙ཽ琮䟩゜灺昉ᮮ⣓燥䤩拐\u0B91䬔⏠ℕ弸璕\u09BA砐嶰\u0A78\u0B65浈呥ƌ伤⍘瓤᭼値淝吜ጐ哰樭璾ಓ礊Ⰴង㭆ᒨ烶ϖ珼䒀㖱秃㰪氵òూᒢิ映买䈵\u23F8\u18FC氋篦悴礎⣵⽘ṵᧀ武ᑲ६䚅䌢ẉ➡у䃐⥭\u2073\u206D⽂加傏䠬ጅ后䃙滅Ҵ儣ᢌׄହ㗣欥栻⇄ͼ墖ᾱᗊ\u05CD爯8⳺ខढ‱ࠢ桉̒\u1774濫‣堦岛劦撨\u0DC8僴ᥠ䪟ⷢ䈂\u1316㸺妯哉塎扂乑ƀ浣⑮摋懭݈༦嵫䙂巁怨搘䠰\u0B00䢠⊿牅栲ȃỮἰ㬹嬱䆚ᅀ毛㑠喅挠᎒⣳ॲ厸淤惢呞⩍䨵Ⅻኸ㗗㴩碣ᖴ垹Ὀ監㞩乙ŕ懱♸浠ᖨ孫潜ᣇ֨噀⑇眵䁚Ч䙋䒖㒑㧣䜣㤾ぃ烪循̢\u0A61䬩ㇰ⟤⊈㴅㝕‥ᤇ捘■\u05C8窫悪䁛⫣䦯殄㒠洡㑕䔜哉㐎࿘㻁稠͝ᘨዱৠ滈懤圮⅔µᔂ\u2BE0㬃古扁氶婐ⱬᕅ呣悯\u2E76ኺᆤⷀ䁢敨硙磈Ǫ✛ₜ䩆厵`⒃ቢự祒墲⚻峙Ǒ䦒\u2B79䰇嗳≷ᎊ惝ౄ㑑冢ᡁ᳔╅檰ိ叀箭\u0095㗓◴共ⴀ廤⭙ₔӘஈ\u3101垁䦽ᑱೣϛᮔ挡倧眩ⱱ儒慨۹᎓‰儁䇳▂⒜ૄ⊵夰䒯䃵ᩄᯍ爃禤⩳Iª⃩䎠✅㶷䁷媳步%纥ሺ丶搓Т⻪篦敮枷爌⠩➢ඡ捩㹄Ӛ㰦係峑㼦䚶䁖Ἆ冰૯㫜堢䠮悎\u3103䍀㓉⢋䧣滆§➾⊔䪲આ৴\u2B60秤ښ㑠埄庾穻楌Ġ "
)),
noiseGenerator, octaveMultiplier);
}
/**
* Meant for making maps conform to the Mollweide (elliptical) projection that MimicMap uses.
* @param rectangular A GreasedRegion where "on" represents land and "off" water, using any rectangular projection
* @return a reprojected version of {@code rectangular} that uses an elliptical projection
*/
public static GreasedRegion reprojectToElliptical(GreasedRegion rectangular) {
int width = rectangular.width, height = rectangular.height;
GreasedRegion t = new GreasedRegion(width, height);
double yPos, xPos,
th, thx, thy, lon, lat, ipi = 1.0 / Math.PI,
rx = width * 0.25, irx = 1.0 / rx, hw = width * 0.5,
ry = height * 0.5, iry = 1.0 / ry;
yPos = -ry;
for (int y = 0; y < height; y++, yPos++) {
thx = NumberTools.asin((yPos) * iry);
lon = (thx == Math.PI * 0.5 || thx == Math.PI * -0.5) ? thx : Math.PI * irx * 0.5 / NumberTools.cos(thx);
thy = thx * 2.0;
lat = NumberTools.asin((thy + NumberTools.sin(thy)) * ipi);
xPos = 0;
for (int x = 0; x < width; x++, xPos++) {
th = lon * (xPos - hw);
if (th >= -3.141592653589793 && th <= 3.141592653589793
&& rectangular.contains((int) ((th + 1) * hw), (int) ((lat + 1) * ry))) {
t.insert(x, y);
}
}
}
return t;
}
@Override
public int wrapX(final int x, int y) {
y = Math.max(0, Math.min(y, height - 1));
if(x < edges[y << 1])
return edges[y << 1 | 1];
else if(x > edges[y << 1 | 1])
return edges[y << 1];
else return x;
}
@Override
public int wrapY(final int x, final int y) {
return Math.max(0, Math.min(y, height - 1));
}
protected void regenerate(int startX, int startY, int usedWidth, int usedHeight,
double landMod, double coolMod, int stateA, int stateB)
{
boolean fresh = false;
if(cacheA != stateA || cacheB != stateB || landMod != landModifier || coolMod != coolingModifier)
{
minHeight = Double.POSITIVE_INFINITY;
maxHeight = Double.NEGATIVE_INFINITY;
minHeat0 = Double.POSITIVE_INFINITY;
maxHeat0 = Double.NEGATIVE_INFINITY;
minHeat1 = Double.POSITIVE_INFINITY;
maxHeat1 = Double.NEGATIVE_INFINITY;
minHeat = Double.POSITIVE_INFINITY;
maxHeat = Double.NEGATIVE_INFINITY;
minWet0 = Double.POSITIVE_INFINITY;
maxWet0 = Double.NEGATIVE_INFINITY;
minWet = Double.POSITIVE_INFINITY;
maxWet = Double.NEGATIVE_INFINITY;
cacheA = stateA;
cacheB = stateB;
fresh = true;
}
rng.setState(stateA, stateB);
long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong();
int t;
landModifier = (landMod <= 0) ? rng.nextDouble(0.29) + 0.91 : landMod;
coolingModifier = (coolMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : coolMod;
earth.remake(earthOriginal);
if(zoom > 0)
{
int stx = Math.min(Math.max((zoomStartX - (width >> 1)) / ((2 << zoom) - 2), 0), width ),
sty = Math.min(Math.max((zoomStartY - (height >> 1)) / ((2 << zoom) - 2), 0), height);
for (int z = 0; z < zoom; z++) {
earth.zoom(stx, sty).expand8way().fray(0.5).expand();
}
coast.remake(earth).not().fringe(2 << zoom).expand().fray(0.5);
shallow.remake(earth).fringe(2 << zoom).expand().fray(0.5);
}
else
{
coast.remake(earth).not().fringe(2);
shallow.remake(earth).fringe(2);
}
double p,
ps, pc,
qs, qc,
h, temp, yPos, xPos,
i_uw = usedWidth / (double)width,
i_uh = usedHeight / (double)height,
th, thx, thy, lon, lat, ipi = 1.0 / Math.PI,
rx = width * 0.25, irx = 1.0 / rx, hw = width * 0.5,
ry = height * 0.5, iry = 1.0 / ry;
yPos = startY - ry;
for (int y = 0; y < height; y++, yPos += i_uh) {
thx = NumberTools.asin((yPos) * iry);
lon = (thx == Math.PI * 0.5 || thx == Math.PI * -0.5) ? thx : Math.PI * irx * 0.5 / NumberTools.cos(thx);
thy = thx * 2.0;
lat = NumberTools.asin((thy + NumberTools.sin(thy)) * ipi);
qc = NumberTools.cos(lat);
qs = NumberTools.sin(lat);
boolean inSpace = true;
xPos = startX;
for (int x = 0/*, xt = 0*/; x < width; x++, xPos += i_uw) {
th = lon * (xPos - hw);
if(th < -3.141592653589793 || th > 3.141592653589793) {
heightCodeData[x][y] = 10000;
inSpace = true;
continue;
}
if(inSpace)
{
inSpace = false;
edges[y << 1] = x;
}
edges[y << 1 | 1] = x;
// th += centerLongitude;
ps = NumberTools.sin(th) * qc;
pc = NumberTools.cos(th) * qc;
xPositions[x][y] = pc;
yPositions[x][y] = ps;
zPositions[x][y] = qs;
if(earth.contains(x, y))
{
h = NumberTools.swayTight(terrainLayered.getNoiseWithSeed(pc + terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5,
ps, qs, seedA)) * 0.85;
if(coast.contains(x, y))
h += 0.05;
else
h += 0.15;
}
else
{
h = NumberTools.swayTight(terrainLayered.getNoiseWithSeed(pc + terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5,
ps, qs, seedA)) * -0.9;
if(shallow.contains(x, y))
h = (h - 0.08) * 0.375;
else
h = (h - 0.125) * 0.75;
}
//h += landModifier - 1.0;
heightData[x][y] = h;
heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps
+ otherRidged.getNoiseWithSeed(pc, ps, qs,seedB + seedC)
, qs, seedB));
moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs
+ otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA)
, seedC));
minHeightActual = Math.min(minHeightActual, h);
maxHeightActual = Math.max(maxHeightActual, h);
if(fresh) {
minHeight = Math.min(minHeight, h);
maxHeight = Math.max(maxHeight, h);
minHeat0 = Math.min(minHeat0, p);
maxHeat0 = Math.max(maxHeat0, p);
minWet0 = Math.min(minWet0, temp);
maxWet0 = Math.max(maxWet0, temp);
}
}
minHeightActual = Math.min(minHeightActual, minHeight);
maxHeightActual = Math.max(maxHeightActual, maxHeight);
}
double heightDiff = 2.0 / (maxHeightActual - minHeightActual),
heatDiff = 0.8 / (maxHeat0 - minHeat0),
wetDiff = 1.0 / (maxWet0 - minWet0),
hMod,
halfHeight = (height - 1) * 0.5, i_half = 1.0 / (halfHeight);
double minHeightActual0 = minHeightActual;
double maxHeightActual0 = maxHeightActual;
yPos = startY + i_uh;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++, yPos += i_uh) {
temp = Math.pow(Math.abs(yPos - halfHeight) * i_half, 1.5);
temp *= (2.4 - temp);
temp = 2.2 - temp;
for (int x = 0; x < width; x++) {
// heightData[x][y] = (h = (heightData[x][y] - minHeightActual) * heightDiff - 1.0);
// minHeightActual0 = Math.min(minHeightActual0, h);
// maxHeightActual0 = Math.max(maxHeightActual0, h);
h = heightData[x][y];
if(heightCodeData[x][y] == 10000) {
heightCodeData[x][y] = 1000;
continue;
}
else
heightCodeData[x][y] = (t = codeHeight(h));
hMod = 1.0;
switch (t) {
case 0:
case 1:
case 2:
case 3:
h = 0.4;
hMod = 0.2;
break;
case 6:
h = -0.1 * (h - forestLower - 0.08);
break;
case 7:
h *= -0.25;
break;
case 8:
h *= -0.4;
break;
default:
h *= 0.05;
}
heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp);
if (fresh) {
ps = Math.min(ps, h); //minHeat0
pc = Math.max(pc, h); //maxHeat0
}
}
}
if(fresh)
{
minHeat1 = ps;
maxHeat1 = pc;
}
heatDiff = coolingModifier / (maxHeat1 - minHeat1);
qs = Double.POSITIVE_INFINITY;
qc = Double.NEGATIVE_INFINITY;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff));
moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff);
if (fresh) {
qs = Math.min(qs, h);
qc = Math.max(qc, h);
ps = Math.min(ps, temp);
pc = Math.max(pc, temp);
}
}
}
if(fresh)
{
minHeat = qs;
maxHeat = qc;
minWet = ps;
maxWet = pc;
}
landData.refill(heightCodeData, 4, 999);
}
}
/**
* A concrete implementation of {@link WorldMapGenerator} that imitates an infinite-distance perspective view of a
* world, showing only one hemisphere, that should be as wide as it is tall (its outline is a circle). This uses an
* Orthographic projection with
* the latitude always at the equator.
* Example views of 50 planets.
*/
@Beta
public static class SpaceViewMap extends WorldMapGenerator {
// protected static final double terrainFreq = 1.65, terrainRidgedFreq = 1.8, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375, riverRidgedFreq = 21.7;
protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375;
protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY,
minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY,
minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY;
public final Noise3D terrain, heat, moisture, otherRidged, terrainLayered;
public final double[][] xPositions,
yPositions,
zPositions;
protected final int[] edges;
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space,
* showing only one hemisphere at a time.
* Always makes a 100x100 map.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
* If you were using {@link SpaceViewMap#SpaceViewMap(long, int, int, Noise3D, double)}, then this would be the
* same as passing the parameters {@code 0x1337BABE1337D00DL, 100, 100, FastNoise.instance, 1.0}.
*/
public SpaceViewMap() {
this(0x1337BABE1337D00DL, 100, 100, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space,
* showing only one hemisphere at a time.
* Takes only the width/height of the map. The initial seed is set to the same large long
* every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and
* height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public SpaceViewMap(int mapWidth, int mapHeight) {
this(0x1337BABE1337D00DL, mapWidth, mapHeight, FastNoise.instance,1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space,
* showing only one hemisphere at a time.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public SpaceViewMap(long initialSeed, int mapWidth, int mapHeight) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space,
* showing only one hemisphere at a time.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with the given octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public SpaceViewMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, octaveMultiplier);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space,
* showing only one hemisphere at a time.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses the given noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
*/
public SpaceViewMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) {
this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space,
* showing only one hemisphere at a time.
* Takes an initial seed, the width/height of the map, and parameters for noise
* generation (a {@link Noise3D} implementation, which is usually {@link FastNoise#instance}, and a
* multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers
* producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used,
* since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map
* cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for
* {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the
* seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance}
* because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should
* probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on
* generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps
* that don't require zooming.
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public SpaceViewMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier) {
super(initialSeed, mapWidth, mapHeight);
xPositions = new double[width][height];
yPositions = new double[width][height];
zPositions = new double[width][height];
edges = new int[height << 1];
terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq);
terrainLayered = new Noise.InverseLayered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325, 0.475);
// terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 8), terrainFreq);
// terrainLayered = new Noise.Layered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 5.25, 0.475);
heat = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75);
moisture = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55);
otherRidged = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq);
}
@Override
public int wrapX(int x, int y) {
y = Math.max(0, Math.min(y, height - 1));
return Math.max(edges[y << 1], Math.min(x, edges[y << 1 | 1]));
}
@Override
public int wrapY(final int x, final int y) {
return Math.max(0, Math.min(y, height - 1));
}
//private static final double root2 = Math.sqrt(2.0), inverseRoot2 = 1.0 / root2, halfInverseRoot2 = 0.5 / root2;
protected void regenerate(int startX, int startY, int usedWidth, int usedHeight,
double landMod, double coolMod, int stateA, int stateB)
{
boolean fresh = false;
if(cacheA != stateA || cacheB != stateB || landMod != landModifier || coolMod != coolingModifier)
{
minHeight = Double.POSITIVE_INFINITY;
maxHeight = Double.NEGATIVE_INFINITY;
minHeightActual = Double.POSITIVE_INFINITY;
maxHeightActual = Double.NEGATIVE_INFINITY;
minHeat0 = Double.POSITIVE_INFINITY;
maxHeat0 = Double.NEGATIVE_INFINITY;
minHeat1 = Double.POSITIVE_INFINITY;
maxHeat1 = Double.NEGATIVE_INFINITY;
minHeat = Double.POSITIVE_INFINITY;
maxHeat = Double.NEGATIVE_INFINITY;
minWet0 = Double.POSITIVE_INFINITY;
maxWet0 = Double.NEGATIVE_INFINITY;
minWet = Double.POSITIVE_INFINITY;
maxWet = Double.NEGATIVE_INFINITY;
cacheA = stateA;
cacheB = stateB;
fresh = true;
}
rng.setState(stateA, stateB);
long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong();
int t;
landModifier = (landMod <= 0) ? rng.nextDouble(0.2) + 0.91 : landMod;
coolingModifier = (coolMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : coolMod;
double p,
ps, pc,
qs, qc,
h, temp, yPos, xPos, iyPos, ixPos,
i_uw = usedWidth / (double)width,
i_uh = usedHeight / (double)height,
th, lon, lat, rho,
rx = width * 0.5, irx = i_uw / rx,
ry = height * 0.5, iry = i_uh / ry;
yPos = startY - ry;
iyPos = yPos / ry;
for (int y = 0; y < height; y++, yPos += i_uh, iyPos += iry) {
boolean inSpace = true;
xPos = startX - rx;
ixPos = xPos / rx;
for (int x = 0; x < width; x++, xPos += i_uw, ixPos += irx) {
rho = Math.sqrt(ixPos * ixPos + iyPos * iyPos);
if(rho > 1.0) {
heightCodeData[x][y] = 10000;
inSpace = true;
continue;
}
if(inSpace)
{
inSpace = false;
edges[y << 1] = x;
}
edges[y << 1 | 1] = x;
th = NumberTools.asin(rho); // c
lat = NumberTools.asin(iyPos);
lon = centerLongitude + NumberTools.atan2(ixPos * rho, rho * NumberTools.cos(th));
qc = NumberTools.cos(lat);
qs = NumberTools.sin(lat);
pc = NumberTools.cos(lon) * qc;
ps = NumberTools.sin(lon) * qc;
xPositions[x][y] = pc;
yPositions[x][y] = ps;
zPositions[x][y] = qs;
heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(pc +
terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5,
ps, qs, seedA) + landModifier - 1.0);
// heightData[x][y] = (h = terrain4D.getNoiseWithSeed(pc, ps, qs,
// (terrainLayered.getNoiseWithSeed(pc, ps, qs, seedB - seedA)
// + terrain.getNoiseWithSeed(pc, ps, qs, seedC - seedB)) * 0.5,
// seedA) * landModifier);
heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps
+ otherRidged.getNoiseWithSeed(pc, ps, qs,seedB + seedC)
, qs, seedB));
moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs
+ otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA)
, seedC));
minHeightActual = Math.min(minHeightActual, h);
maxHeightActual = Math.max(maxHeightActual, h);
if(fresh) {
minHeight = Math.min(minHeight, h);
maxHeight = Math.max(maxHeight, h);
minHeat0 = Math.min(minHeat0, p);
maxHeat0 = Math.max(maxHeat0, p);
minWet0 = Math.min(minWet0, temp);
maxWet0 = Math.max(maxWet0, temp);
}
}
minHeightActual = Math.min(minHeightActual, minHeight);
maxHeightActual = Math.max(maxHeightActual, maxHeight);
}
double heatDiff = 0.8 / (maxHeat0 - minHeat0),
wetDiff = 1.0 / (maxWet0 - minWet0),
hMod,
halfHeight = (height - 1) * 0.5, i_half = 1.0 / halfHeight;
yPos = startY + i_uh;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++, yPos += i_uh) {
temp = Math.abs(yPos - halfHeight) * i_half;
temp *= (2.4 - temp);
temp = 2.2 - temp;
for (int x = 0; x < width; x++) {
h = heightData[x][y];
if(heightCodeData[x][y] == 10000) {
heightCodeData[x][y] = 1000;
continue;
}
else
heightCodeData[x][y] = (t = codeHeight(h));
hMod = 1.0;
switch (t) {
case 0:
case 1:
case 2:
case 3:
h = 0.4;
hMod = 0.2;
break;
case 6:
h = -0.1 * (h - forestLower - 0.08);
break;
case 7:
h *= -0.25;
break;
case 8:
h *= -0.4;
break;
default:
h *= 0.05;
}
heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp);
if (fresh) {
ps = Math.min(ps, h); //minHeat0
pc = Math.max(pc, h); //maxHeat0
}
}
}
if(fresh)
{
minHeat1 = ps;
maxHeat1 = pc;
}
heatDiff = coolingModifier / (maxHeat1 - minHeat1);
qs = Double.POSITIVE_INFINITY;
qc = Double.NEGATIVE_INFINITY;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff));
moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff);
if (fresh) {
qs = Math.min(qs, h);
qc = Math.max(qc, h);
ps = Math.min(ps, temp);
pc = Math.max(pc, temp);
}
}
}
if(fresh)
{
minHeat = qs;
maxHeat = qc;
minWet = ps;
maxWet = pc;
}
landData.refill(heightCodeData, 4, 999);
}
}
/**
* A concrete implementation of {@link WorldMapGenerator} that projects the world map onto a shape with a flat top
* and bottom but near-circular sides. This is an equal-area projection, like EllipticalMap, so effects that fill
* areas on a map like {@link PoliticalMapper} will fill (almost) equally on any part of the map. This has less
* distortion on the far left and far right edges of the map than EllipticalMap, but the flat top and bottom are
* probably very distorted in a small area near the poles.
* This uses the Eckert IV projection.
* Example map
*/
@Beta
public static class RoundSideMap extends WorldMapGenerator {
// protected static final double terrainFreq = 1.35, terrainRidgedFreq = 1.8, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375, riverRidgedFreq = 21.7;
protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375;
protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY,
minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY,
minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY;
public final Noise3D terrain, heat, moisture, otherRidged, terrainLayered;
public final double[][] xPositions,
yPositions,
zPositions;
protected final int[] edges;
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Always makes a 200x100 map.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
* If you were using {@link RoundSideMap#RoundSideMap(long, int, int, Noise3D, double)}, then this would be the
* same as passing the parameters {@code 0x1337BABE1337D00DL, 200, 100, FastNoise.instance, 1.0}.
*/
public RoundSideMap() {
this(0x1337BABE1337D00DL, 200, 100, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes only the width/height of the map. The initial seed is set to the same large long
* every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and
* height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public RoundSideMap(int mapWidth, int mapHeight) {
this(0x1337BABE1337D00DL, mapWidth, mapHeight, FastNoise.instance,1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public RoundSideMap(long initialSeed, int mapWidth, int mapHeight) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with the given octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public RoundSideMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, octaveMultiplier);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. The suggested Noise3D
* implementation to use is {@link FastNoise#instance}
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
*/
public RoundSideMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) {
this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes an initial seed, the width/height of the map, and parameters for noise generation (a
* {@link Noise3D} implementation, where {@link FastNoise#instance} is suggested, and a
* multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers
* producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used,
* since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map
* cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for
* {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the
* seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance}
* because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should
* probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on
* generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps
* that don't require zooming.
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public RoundSideMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier) {
super(initialSeed, mapWidth, mapHeight);
xPositions = new double[width][height];
yPositions = new double[width][height];
zPositions = new double[width][height];
edges = new int[height << 1];
terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq);
terrainLayered = new Noise.InverseLayered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325);
// terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 8), terrainFreq);
// terrainLayered = new Noise.Layered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 5.25);
heat = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75);
moisture = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55);
otherRidged = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq);
}
@Override
public int wrapX(final int x, int y) {
y = Math.max(0, Math.min(y, height - 1));
if(x < edges[y << 1])
return edges[y << 1 | 1];
else if(x > edges[y << 1 | 1])
return edges[y << 1];
else return x;
}
@Override
public int wrapY(final int x, final int y) {
return Math.max(0, Math.min(y, height - 1));
}
protected void regenerate(int startX, int startY, int usedWidth, int usedHeight,
double landMod, double coolMod, int stateA, int stateB)
{
boolean fresh = false;
if(cacheA != stateA || cacheB != stateB || landMod != landModifier || coolMod != coolingModifier)
{
minHeight = Double.POSITIVE_INFINITY;
maxHeight = Double.NEGATIVE_INFINITY;
minHeightActual = Double.POSITIVE_INFINITY;
maxHeightActual = Double.NEGATIVE_INFINITY;
minHeat0 = Double.POSITIVE_INFINITY;
maxHeat0 = Double.NEGATIVE_INFINITY;
minHeat1 = Double.POSITIVE_INFINITY;
maxHeat1 = Double.NEGATIVE_INFINITY;
minHeat = Double.POSITIVE_INFINITY;
maxHeat = Double.NEGATIVE_INFINITY;
minWet0 = Double.POSITIVE_INFINITY;
maxWet0 = Double.NEGATIVE_INFINITY;
minWet = Double.POSITIVE_INFINITY;
maxWet = Double.NEGATIVE_INFINITY;
cacheA = stateA;
cacheB = stateB;
fresh = true;
}
rng.setState(stateA, stateB);
long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong();
int t;
landModifier = (landMod <= 0) ? rng.nextDouble(0.2) + 0.91 : landMod;
coolingModifier = (coolMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : coolMod;
double p,
ps, pc,
qs, qc,
h, temp, yPos, xPos,
i_uw = usedWidth / (double)width,
i_uh = usedHeight / (double)height,
th, thb, thx, thy, lon, lat,
rx = width * 0.25, irx = 1.326500428177002 / rx, hw = width * 0.5,
ry = height * 0.5, iry = 1.0 / ry;
yPos = startY - ry;
for (int y = 0; y < height; y++, yPos += i_uh) {
thy = yPos * iry;//NumberTools.sin(thb);
thb = NumberTools.asin(thy);
thx = NumberTools.cos(thb);
//1.3265004 0.7538633073600218 1.326500428177002
lon = (thx == Math.PI * 0.5 || thx == Math.PI * -0.5) ? 0x1.0p100 : irx / (0.42223820031577125 * (1.0 + thx));
qs = (thb + (thx + 2.0) * thy) * 0.2800495767557787;
lat = NumberTools.asin(qs);
qc = NumberTools.cos(lat);
boolean inSpace = true;
xPos = startX - hw;
for (int x = 0/*, xt = 0*/; x < width; x++, xPos += i_uw) {
th = lon * xPos;
if(th < -3.141592653589793 || th > 3.141592653589793) {
heightCodeData[x][y] = 10000;
inSpace = true;
continue;
}
if(inSpace)
{
inSpace = false;
edges[y << 1] = x;
}
edges[y << 1 | 1] = x;
th += centerLongitude;
ps = NumberTools.sin(th) * qc;
pc = NumberTools.cos(th) * qc;
xPositions[x][y] = pc;
yPositions[x][y] = ps;
zPositions[x][y] = qs;
heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(pc +
terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5,
ps, qs, seedA) + landModifier - 1.0);
heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps
+ otherRidged.getNoiseWithSeed(pc, ps, qs,seedB + seedC)
, qs, seedB));
moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs
+ otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA)
, seedC));
minHeightActual = Math.min(minHeightActual, h);
maxHeightActual = Math.max(maxHeightActual, h);
if(fresh) {
minHeight = Math.min(minHeight, h);
maxHeight = Math.max(maxHeight, h);
minHeat0 = Math.min(minHeat0, p);
maxHeat0 = Math.max(maxHeat0, p);
minWet0 = Math.min(minWet0, temp);
maxWet0 = Math.max(maxWet0, temp);
}
}
minHeightActual = Math.min(minHeightActual, minHeight);
maxHeightActual = Math.max(maxHeightActual, maxHeight);
}
double heatDiff = 0.8 / (maxHeat0 - minHeat0),
wetDiff = 1.0 / (maxWet0 - minWet0),
hMod,
halfHeight = (height - 1) * 0.5, i_half = 1.0 / halfHeight;
yPos = startY + i_uh;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++, yPos += i_uh) {
temp = Math.abs(yPos - halfHeight) * i_half;
temp *= (2.4 - temp);
temp = 2.2 - temp;
for (int x = 0; x < width; x++) {
h = heightData[x][y];
if(heightCodeData[x][y] == 10000) {
heightCodeData[x][y] = 1000;
continue;
}
else
heightCodeData[x][y] = (t = codeHeight(h));
hMod = 1.0;
switch (t) {
case 0:
case 1:
case 2:
case 3:
h = 0.4;
hMod = 0.2;
break;
case 6:
h = -0.1 * (h - forestLower - 0.08);
break;
case 7:
h *= -0.25;
break;
case 8:
h *= -0.4;
break;
default:
h *= 0.05;
}
heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp);
if (fresh) {
ps = Math.min(ps, h); //minHeat0
pc = Math.max(pc, h); //maxHeat0
}
}
}
if(fresh)
{
minHeat1 = ps;
maxHeat1 = pc;
}
heatDiff = coolingModifier / (maxHeat1 - minHeat1);
qs = Double.POSITIVE_INFINITY;
qc = Double.NEGATIVE_INFINITY;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff));
moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff);
if (fresh) {
qs = Math.min(qs, h);
qc = Math.max(qc, h);
ps = Math.min(ps, temp);
pc = Math.max(pc, temp);
}
}
}
if(fresh)
{
minHeat = qs;
maxHeat = qc;
minWet = ps;
maxWet = pc;
}
landData.refill(heightCodeData, 4, 999);
}
}
/**
* A concrete implementation of {@link WorldMapGenerator} that projects the world map onto a shape that resembles a
* mix part-way between an ellipse and a rectangle. This is an equal-area projection, like EllipticalMap, so effects that fill
* areas on a map like {@link PoliticalMapper} will fill (almost) equally on any part of the map. This has less
* distortion around all the edges than the other maps here, especially when comparing the North and South poles
* with RoundSideMap.
* This uses the Tobler hyperelliptical projection.
* Example map
*/
@Beta
public static class HyperellipticalMap extends WorldMapGenerator {
protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375;
protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY,
minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY,
minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY;
public final Noise3D terrain, heat, moisture, otherRidged, terrainLayered;
public final double[][] xPositions,
yPositions,
zPositions;
protected final int[] edges;
private final double alpha, kappa, epsilon;
private final double[] Z;
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Always makes a 200x100 map.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
* If you were using {@link HyperellipticalMap#HyperellipticalMap(long, int, int, Noise3D, double)}, then this would be the
* same as passing the parameters {@code 0x1337BABE1337D00DL, 200, 100, FastNoise.instance, 1.0}.
*/
public HyperellipticalMap() {
this(0x1337BABE1337D00DL, 200, 100, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes only the width/height of the map. The initial seed is set to the same large long
* every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and
* height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public HyperellipticalMap(int mapWidth, int mapHeight) {
this(0x1337BABE1337D00DL, mapWidth, mapHeight, FastNoise.instance,1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public HyperellipticalMap(long initialSeed, int mapWidth, int mapHeight) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with the given octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public HyperellipticalMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, octaveMultiplier);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. The suggested Noise3D
* implementation to use is {@link FastNoise#instance}.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
*/
public HyperellipticalMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) {
this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes an initial seed, the width/height of the map, and parameters for noise generation (a
* {@link Noise3D} implementation, where {@link FastNoise#instance} is suggested, and a
* multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers
* producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used,
* since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map
* cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for
* {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the
* seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance}
* because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should
* probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on
* generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps
* that don't require zooming.
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public HyperellipticalMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier){
this(initialSeed, mapWidth, mapHeight, noiseGenerator, octaveMultiplier, 0.0625, 2.5);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape.
* Takes an initial seed, the width/height of the map, and parameters for noise generation (a
* {@link Noise3D} implementation, where {@link FastNoise#instance} is suggested, and a
* multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers
* producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used,
* since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map
* cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for
* {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the
* seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance}
* because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should
* probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on
* generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps
* that don't require zooming.
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
* @param alpha one of the Tobler parameters; 0.0625 is the default and this can range from 0.0 to 1.0 at least
* @param kappa one of the Tobler parameters; 2.5 is the default but 2.0-5.0 range values are also often used
*/
public HyperellipticalMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator,
double octaveMultiplier, double alpha, double kappa){
super(initialSeed, mapWidth, mapHeight);
xPositions = new double[width][height];
yPositions = new double[width][height];
zPositions = new double[width][height];
edges = new int[height << 1];
terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq);
terrainLayered = new Noise.InverseLayered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325);
heat = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75);
moisture = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55);
otherRidged = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq);
this.alpha = alpha;
this.kappa = kappa;
this.Z = new double[height << 2];
this.epsilon = ProjectionTools.simpsonIntegrateHyperellipse(0.0, 1.0, 0.25 / height, kappa);
ProjectionTools.simpsonODESolveHyperellipse(1, this.Z, 0.25 / height, alpha, kappa, epsilon);
}
@Override
public int wrapX(final int x, int y) {
y = Math.max(0, Math.min(y, height - 1));
if(x < edges[y << 1])
return edges[y << 1 | 1];
else if(x > edges[y << 1 | 1])
return edges[y << 1];
else return x;
}
@Override
public int wrapY(final int x, final int y) {
return Math.max(0, Math.min(y, height - 1));
}
/**
* Given a latitude and longitude in radians (the conventional way of describing points on a globe), this gets the
* (x,y) Coord on the map projection this generator uses that corresponds to the given lat-lon coordinates. If this
* generator does not represent a globe (if it is toroidal, for instance) or if there is no "good way" to calculate
* the projection for a given lat-lon coordinate, this returns null. This implementation never returns null.
* If this is a supported operation and the parameters are valid, this returns a Coord with x between 0 and
* {@link #width}, and y between 0 and {@link #height}, both exclusive. Automatically wraps the Coord's values using
* {@link #wrapX(int, int)} and {@link #wrapY(int, int)}.
*
* @param latitude the latitude, from {@code Math.PI * -0.5} to {@code Math.PI * 0.5}
* @param longitude the longitude, from {@code 0.0} to {@code Math.PI * 2.0}
* @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
*/
@Override
public Coord project(double latitude, double longitude) {
final double z0 = Math.abs(NumberTools.sin(latitude));
final int i = Arrays.binarySearch(Z, z0);
final double y;
if (i >= 0)
y = i/(Z.length-1.);
else if (-i-1 >= Z.length)
y = Z[Z.length-1];
else
y = ((z0-Z[-i-2])/(Z[-i-1]-Z[-i-2]) + (-i-2))/(Z.length-1.);
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);
final int yy = (int)(y * Math.signum(latitude) * height * 0.5 + 0.5);
return Coord.get(wrapX(xx, yy), wrapY(xx, yy));
}
protected void regenerate(int startX, int startY, int usedWidth, int usedHeight,
double landMod, double coolMod, int stateA, int stateB)
{
boolean fresh = false;
if(cacheA != stateA || cacheB != stateB || landMod != landModifier || coolMod != coolingModifier)
{
minHeight = Double.POSITIVE_INFINITY;
maxHeight = Double.NEGATIVE_INFINITY;
minHeightActual = Double.POSITIVE_INFINITY;
maxHeightActual = Double.NEGATIVE_INFINITY;
minHeat0 = Double.POSITIVE_INFINITY;
maxHeat0 = Double.NEGATIVE_INFINITY;
minHeat1 = Double.POSITIVE_INFINITY;
maxHeat1 = Double.NEGATIVE_INFINITY;
minHeat = Double.POSITIVE_INFINITY;
maxHeat = Double.NEGATIVE_INFINITY;
minWet0 = Double.POSITIVE_INFINITY;
maxWet0 = Double.NEGATIVE_INFINITY;
minWet = Double.POSITIVE_INFINITY;
maxWet = Double.NEGATIVE_INFINITY;
cacheA = stateA;
cacheB = stateB;
fresh = true;
}
rng.setState(stateA, stateB);
long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong();
int t;
landModifier = (landMod <= 0) ? rng.nextDouble(0.2) + 0.91 : landMod;
coolingModifier = (coolMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : coolMod;
double p,
ps, pc,
qs, qc,
h, temp, yPos, xPos,
i_uw = usedWidth / (double)width,
i_uh = usedHeight / (double)height,
th, lon,
rx = width * 0.5, irx = Math.PI / rx, hw = width * 0.5,
ry = height * 0.5, iry = 1.0 / ry;
yPos = startY - ry;
for (int y = 0; y < height; y++, yPos += i_uh) {
// thy = yPos * iry;//NumberTools.sin(thb);
// thb = asin(thy);
// thx = NumberTools.cos(thb);
// //1.3265004 0.7538633073600218 1.326500428177002
// lon = (thx == Math.PI * 0.5 || thx == Math.PI * -0.5) ? 0x1.0p100 : irx / (0.42223820031577125 * (1.0 + thx));
// qs = (thb + (thx + 2.0) * thy) * 0.2800495767557787;
// lat = asin(qs);
//
// qc = NumberTools.cos(lat);
lon = NumberTools.asin(Z[(int)(0.5 + Math.abs(yPos*iry)*(Z.length-1))])*Math.signum(yPos);
qs = NumberTools.sin(lon);
qc = NumberTools.cos(lon);
boolean inSpace = true;
xPos = startX - hw;
for (int x = 0/*, xt = 0*/; x < width; x++, xPos += i_uw) {
//th = lon * xPos;
th = xPos * irx / Math.abs(alpha + (1-alpha)*ProjectionTools.hyperellipse(yPos * iry, kappa));
if(th < -3.141592653589793 || th > 3.141592653589793) {
//if(th < -2.0 || th > 2.0) {
heightCodeData[x][y] = 10000;
inSpace = true;
continue;
}
if(inSpace)
{
inSpace = false;
edges[y << 1] = x;
}
edges[y << 1 | 1] = x;
th += centerLongitude;
ps = NumberTools.sin(th) * qc;
pc = NumberTools.cos(th) * qc;
xPositions[x][y] = pc;
yPositions[x][y] = ps;
zPositions[x][y] = qs;
heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(pc +
terrain.getNoiseWithSeed(pc, ps, qs, seedB - seedA) * 0.5,
ps, qs, seedA) + landModifier - 1.0);
heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps
+ otherRidged.getNoiseWithSeed(pc, ps, qs, seedB + seedC)
, qs, seedB));
moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs
+ otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA)
, seedC));
minHeightActual = Math.min(minHeightActual, h);
maxHeightActual = Math.max(maxHeightActual, h);
if(fresh) {
minHeight = Math.min(minHeight, h);
maxHeight = Math.max(maxHeight, h);
minHeat0 = Math.min(minHeat0, p);
maxHeat0 = Math.max(maxHeat0, p);
minWet0 = Math.min(minWet0, temp);
maxWet0 = Math.max(maxWet0, temp);
}
}
minHeightActual = Math.min(minHeightActual, minHeight);
maxHeightActual = Math.max(maxHeightActual, maxHeight);
}
double heatDiff = 0.8 / (maxHeat0 - minHeat0),
wetDiff = 1.0 / (maxWet0 - minWet0),
hMod,
halfHeight = (height - 1) * 0.5, i_half = 1.0 / halfHeight;
yPos = startY + i_uh;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++, yPos += i_uh) {
temp = Math.abs(yPos - halfHeight) * i_half;
temp *= (2.4 - temp);
temp = 2.2 - temp;
for (int x = 0; x < width; x++) {
h = heightData[x][y];
if(heightCodeData[x][y] == 10000) {
heightCodeData[x][y] = 1000;
continue;
}
else
heightCodeData[x][y] = (t = codeHeight(h));
hMod = 1.0;
switch (t) {
case 0:
case 1:
case 2:
case 3:
h = 0.4;
hMod = 0.2;
break;
case 6:
h = -0.1 * (h - forestLower - 0.08);
break;
case 7:
h *= -0.25;
break;
case 8:
h *= -0.4;
break;
default:
h *= 0.05;
}
heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp);
if (fresh) {
ps = Math.min(ps, h); //minHeat0
pc = Math.max(pc, h); //maxHeat0
}
}
}
if(fresh)
{
minHeat1 = ps;
maxHeat1 = pc;
}
heatDiff = coolingModifier / (maxHeat1 - minHeat1);
qs = Double.POSITIVE_INFINITY;
qc = Double.NEGATIVE_INFINITY;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff));
moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff);
if (fresh) {
qs = Math.min(qs, h);
qc = Math.max(qc, h);
ps = Math.min(ps, temp);
pc = Math.max(pc, temp);
}
}
}
if(fresh)
{
minHeat = qs;
maxHeat = qc;
minWet = ps;
maxWet = pc;
}
landData.refill(heightCodeData, 4, 999);
}
}
/**
* A concrete implementation of {@link WorldMapGenerator} that projects the world map onto an ellipse that should be
* twice as wide as it is tall (although you can stretch it by width and height that don't have that ratio).
* This uses the Hammer projection, so the latitude
* lines are curved instead of flat. The Mollweide projection that {@link WorldMapGenerator.EllipticalMap} uses has flat lines, but
* the two projection are otherwise very similar, and are both equal-area (Hammer tends to have less significant
* distortion around the edges, but the curvature of the latitude lines can be hard to visualize).
* Preview image link of a world rotating.
*/
@Beta
public static class EllipticalHammerMap extends WorldMapGenerator {
// protected static final double terrainFreq = 1.35, terrainRidgedFreq = 1.8, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375, riverRidgedFreq = 21.7;
protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375;
protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY,
minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY,
minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY;
public final Noise3D terrain, heat, moisture, otherRidged, terrainLayered;
public final double[][] xPositions,
yPositions,
zPositions;
protected final int[] edges;
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape. This is very
* similar to {@link EllipticalMap}, but has curved latitude lines instead of flat ones (it also may see more
* internal usage because some operations on this projection are much faster and simpler).
* Always makes a 200x100 map.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
* If you were using {@link EllipticalHammerMap#EllipticalHammerMap(long, int, int, Noise3D, double)}, then this would be the
* same as passing the parameters {@code 0x1337BABE1337D00DL, 200, 100, FastNoise.instance, 1.0}.
*/
public EllipticalHammerMap() {
this(0x1337BABE1337D00DL, 200, 100, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape. This is very
* similar to {@link EllipticalMap}, but has curved latitude lines instead of flat ones (it also may see more
* internal usage because some operations on this projection are much faster and simpler).
* Takes only the width/height of the map. The initial seed is set to the same large long
* every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and
* height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public EllipticalHammerMap(int mapWidth, int mapHeight) {
this(0x1337BABE1337D00DL, mapWidth, mapHeight, FastNoise.instance,1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape. This is very
* similar to {@link EllipticalMap}, but has curved latitude lines instead of flat ones (it also may see more
* internal usage because some operations on this projection are much faster and simpler).
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public EllipticalHammerMap(long initialSeed, int mapWidth, int mapHeight) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape. This is very
* similar to {@link EllipticalMap}, but has curved latitude lines instead of flat ones (it also may see more
* internal usage because some operations on this projection are much faster and simpler).
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with the given octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public EllipticalHammerMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, octaveMultiplier);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape. This is very
* similar to {@link EllipticalMap}, but has curved latitude lines instead of flat ones (it also may see more
* internal usage because some operations on this projection are much faster and simpler).
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses the given noise generator, with 1.0 as the octave multiplier affecting detail. The suggested Noise3D
* implementation to use is {@link FastNoise#instance}.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
*/
public EllipticalHammerMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) {
this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to display a projection of a globe onto an
* ellipse without distortion of the sizes of features but with significant distortion of shape. This is very
* similar to {@link EllipticalMap}, but has curved latitude lines instead of flat ones (it also may see more
* internal usage because some operations on this projection are much faster and simpler).
* Takes an initial seed, the width/height of the map, and parameters for noise generation (a
* {@link Noise3D} implementation, where {@link FastNoise#instance} is suggested, and a
* multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers
* producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used,
* since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map
* cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for
* {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the
* seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance}
* because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should
* probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on
* generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps
* that don't require zooming.
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public EllipticalHammerMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier) {
super(initialSeed, mapWidth, mapHeight);
xPositions = new double[width][height];
yPositions = new double[width][height];
zPositions = new double[width][height];
edges = new int[height << 1];
terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq);
terrainLayered = new Noise.InverseLayered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325);
// terrain = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 8), terrainFreq);
// terrainLayered = new Noise.Layered3D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 5.25);
heat = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75);
moisture = new Noise.InverseLayered3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55);
otherRidged = new Noise.Ridged3D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq);
}
@Override
public int wrapX(final int x, int y) {
y = Math.max(0, Math.min(y, height - 1));
if(x < edges[y << 1])
return edges[y << 1 | 1];
else if(x > edges[y << 1 | 1])
return edges[y << 1];
else return x;
}
@Override
public int wrapY(final int x, final int y) {
return Math.max(0, Math.min(y, height - 1));
}
protected void regenerate(int startX, int startY, int usedWidth, int usedHeight,
double landMod, double coolMod, int stateA, int stateB)
{
boolean fresh = false;
if(cacheA != stateA || cacheB != stateB || landMod != landModifier || coolMod != coolingModifier)
{
minHeight = Double.POSITIVE_INFINITY;
maxHeight = Double.NEGATIVE_INFINITY;
minHeightActual = Double.POSITIVE_INFINITY;
maxHeightActual = Double.NEGATIVE_INFINITY;
minHeat0 = Double.POSITIVE_INFINITY;
maxHeat0 = Double.NEGATIVE_INFINITY;
minHeat1 = Double.POSITIVE_INFINITY;
maxHeat1 = Double.NEGATIVE_INFINITY;
minHeat = Double.POSITIVE_INFINITY;
maxHeat = Double.NEGATIVE_INFINITY;
minWet0 = Double.POSITIVE_INFINITY;
maxWet0 = Double.NEGATIVE_INFINITY;
minWet = Double.POSITIVE_INFINITY;
maxWet = Double.NEGATIVE_INFINITY;
cacheA = stateA;
cacheB = stateB;
fresh = true;
}
rng.setState(stateA, stateB);
long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong();
int t;
landModifier = (landMod <= 0) ? rng.nextDouble(0.2) + 0.91 : landMod;
coolingModifier = (coolMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : coolMod;
double p,
ps, pc,
qs, qc,
h, temp, yPos, xPos,
z, th, lon, lat,
rx = width * 0.5, hw = width * 0.5, root2 = Math.sqrt(2.0),
irx = 1.0 / rx, iry = 2.0 / (double) height,
xAdj, yAdj,
i_uw = usedWidth / (double)(width),
i_uh = usedHeight / (double)(height);
yPos = (startY - height * 0.5);
for (int y = 0; y < height; y++, yPos += i_uh) {
boolean inSpace = true;
yAdj = yPos * iry;
xPos = (startX - hw);
for (int x = 0; x < width; x++, xPos += i_uw) {
xAdj = xPos * irx;
z = Math.sqrt(1.0 - 0.5 * xAdj * xAdj - 0.5 * yAdj * yAdj);
th = z * yAdj * root2;
lon = 2.0 * NumberTools.atan2((2.0 * z * z - 1.0), (z * xAdj * root2));
if(th != th || lon < 0.0) {
heightCodeData[x][y] = 10000;
inSpace = true;
continue;
}
lat = NumberTools.asin(th);
qc = NumberTools.cos(lat);
qs = th;
th = Math.PI - lon + centerLongitude;
if(inSpace)
{
inSpace = false;
edges[y << 1] = x;
}
edges[y << 1 | 1] = x;
ps = NumberTools.sin(th) * qc;
pc = NumberTools.cos(th) * qc;
xPositions[x][y] = pc;
yPositions[x][y] = ps;
zPositions[x][y] = qs;
heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(pc +
terrain.getNoiseWithSeed(pc, ps, qs,seedB - seedA) * 0.5,
ps, qs, seedA) + landModifier - 1.0);
heatData[x][y] = (p = heat.getNoiseWithSeed(pc, ps
+ otherRidged.getNoiseWithSeed(pc, ps, qs,seedB + seedC)
, qs, seedB));
moistureData[x][y] = (temp = moisture.getNoiseWithSeed(pc, ps, qs
+ otherRidged.getNoiseWithSeed(pc, ps, qs, seedC + seedA)
, seedC));
minHeightActual = Math.min(minHeightActual, h);
maxHeightActual = Math.max(maxHeightActual, h);
if(fresh) {
minHeight = Math.min(minHeight, h);
maxHeight = Math.max(maxHeight, h);
minHeat0 = Math.min(minHeat0, p);
maxHeat0 = Math.max(maxHeat0, p);
minWet0 = Math.min(minWet0, temp);
maxWet0 = Math.max(maxWet0, temp);
}
}
minHeightActual = Math.min(minHeightActual, minHeight);
maxHeightActual = Math.max(maxHeightActual, maxHeight);
}
double heatDiff = 0.8 / (maxHeat0 - minHeat0),
wetDiff = 1.0 / (maxWet0 - minWet0),
hMod,
halfHeight = (height - 1) * 0.5, i_half = 1.0 / halfHeight;
yPos = startY + i_uh;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++, yPos += i_uh) {
temp = Math.abs(yPos - halfHeight) * i_half;
temp *= (2.4 - temp);
temp = 2.2 - temp;
for (int x = 0; x < width; x++) {
h = heightData[x][y];
if(heightCodeData[x][y] == 10000) {
heightCodeData[x][y] = 1000;
continue;
}
else
heightCodeData[x][y] = (t = codeHeight(h));
hMod = 1.0;
switch (t) {
case 0:
case 1:
case 2:
case 3:
h = 0.4;
hMod = 0.2;
break;
case 6:
h = -0.1 * (h - forestLower - 0.08);
break;
case 7:
h *= -0.25;
break;
case 8:
h *= -0.4;
break;
default:
h *= 0.05;
}
heatData[x][y] = (h = (((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6) * temp);
if (fresh) {
ps = Math.min(ps, h); //minHeat0
pc = Math.max(pc, h); //maxHeat0
}
}
}
if(fresh)
{
minHeat1 = ps;
maxHeat1 = pc;
}
heatDiff = coolingModifier / (maxHeat1 - minHeat1);
qs = Double.POSITIVE_INFINITY;
qc = Double.NEGATIVE_INFINITY;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff));
moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff);
if (fresh) {
qs = Math.min(qs, h);
qc = Math.max(qc, h);
ps = Math.min(ps, temp);
pc = Math.max(pc, temp);
}
}
}
if(fresh)
{
minHeat = qs;
maxHeat = qc;
minWet = ps;
maxWet = pc;
}
landData.refill(heightCodeData, 4, 999);
}
}
/**
* A concrete implementation of {@link WorldMapGenerator} that imitates an infinite-distance perspective view of a
* world, showing only one hemisphere, that should be as wide as it is tall (its outline is a circle). It should
* look as a world would when viewed from space, and implements rotation differently to allow the planet to be
* rotated without recalculating all the data, though it cannot zoom. Note that calling
* {@link #setCenterLongitude(double)} does a lot more work than in other classes, but less than fully calling
* {@link #generate()} in those classes, since it doesn't remake the map data at a slightly different rotation and
* instead keeps a single map in use the whole time, using sections of it. This uses an
* Orthographic projection with
* the latitude always at the equator; the internal map is stored as a {@link WorldMapGenerator.SphereMap}, which uses a
* cylindrical equal-area
* projection, specifically the Smyth equal-surface projection.
*
* Example view of a planet rotating.
* Another example.
*/
@Beta
public static class RotatingSpaceMap extends WorldMapGenerator {
protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY,
minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY,
minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY;
public final double[][] xPositions,
yPositions,
zPositions;
protected final int[] edges;
public final SphereMap storedMap;
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space,
* showing only one hemisphere at a time.
* Always makes a 100x100 map.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
* If you were using {@link RotatingSpaceMap#RotatingSpaceMap(long, int, int, Noise3D, double)}, then this would be the
* same as passing the parameters {@code 0x1337BABE1337D00DL, 100, 100, FastNoise.instance, 1.0}.
*/
public RotatingSpaceMap() {
this(0x1337BABE1337D00DL, 100, 100, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space,
* showing only one hemisphere at a time.
* Takes only the width/height of the map. The initial seed is set to the same large long
* every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and
* height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public RotatingSpaceMap(int mapWidth, int mapHeight) {
this(0x1337BABE1337D00DL, mapWidth, mapHeight, FastNoise.instance,1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space,
* showing only one hemisphere at a time.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public RotatingSpaceMap(long initialSeed, int mapWidth, int mapHeight) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space,
* showing only one hemisphere at a time.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with the given octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public RotatingSpaceMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, octaveMultiplier);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space,
* showing only one hemisphere at a time.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses the given noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
*/
public RotatingSpaceMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator) {
this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to view a spherical world from space,
* showing only one hemisphere at a time.
* Takes an initial seed, the width/height of the map, and parameters for noise
* generation (a {@link Noise3D} implementation, which is usually {@link FastNoise#instance}, and a
* multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers
* producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used,
* since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map
* cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for
* {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the
* seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance}
* because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should
* probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on
* generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps
* that don't require zooming.
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public RotatingSpaceMap(long initialSeed, int mapWidth, int mapHeight, Noise3D noiseGenerator, double octaveMultiplier) {
super(initialSeed, mapWidth, mapHeight);
xPositions = new double[mapWidth][mapHeight];
yPositions = new double[mapWidth][mapHeight];
zPositions = new double[mapWidth][mapHeight];
edges = new int[height << 1];
storedMap = new SphereMap(initialSeed, mapWidth << 1, mapHeight, noiseGenerator, octaveMultiplier);
}
@Override
public int wrapX(int x, int y) {
y = Math.max(0, Math.min(y, height - 1));
return Math.max(edges[y << 1], Math.min(x, edges[y << 1 | 1]));
}
@Override
public int wrapY(final int x, final int y) {
return Math.max(0, Math.min(y, height - 1));
}
@Override
public void setCenterLongitude(double centerLongitude) {
super.setCenterLongitude(centerLongitude);
int ax, ay;
double
ps, pc,
qs, qc,
h, yPos, xPos, iyPos, ixPos,
i_uw = usedWidth / (double)width,
i_uh = usedHeight / (double)height,
th, lon, lat, rho,
i_pi = 1.0 / Math.PI,
rx = width * 0.5, irx = i_uw / rx,
ry = height * 0.5, iry = i_uh / ry;
yPos = startY - ry;
iyPos = yPos / ry;
for (int y = 0; y < height; y++, yPos += i_uh, iyPos += iry) {
boolean inSpace = true;
xPos = startX - rx;
ixPos = xPos / rx;
lat = NumberTools.asin(iyPos);
for (int x = 0; x < width; x++, xPos += i_uw, ixPos += irx) {
rho = (ixPos * ixPos + iyPos * iyPos);
if(rho > 1.0) {
heightCodeData[x][y] = 1000;
inSpace = true;
continue;
}
rho = Math.sqrt(rho);
if(inSpace)
{
inSpace = false;
edges[y << 1] = x;
}
edges[y << 1 | 1] = x;
th = NumberTools.asin(rho); // c
lon = removeExcess((centerLongitude + (NumberTools.atan2(ixPos * rho, rho * NumberTools.cos(th)))) * 0.5);
qs = lat * 0.6366197723675814;
qc = qs + 1.0;
int sf = (qs >= 0.0 ? (int) qs : (int) qs - 1) & -2;
int cf = (qc >= 0.0 ? (int) qc : (int) qc - 1) & -2;
qs -= sf;
qc -= cf;
qs *= 2.0 - qs;
qc *= 2.0 - qc;
qs = qs * (-0.775 - 0.225 * qs) * ((sf & 2) - 1);
qc = qc * (-0.775 - 0.225 * qc) * ((cf & 2) - 1);
ps = lon * 0.6366197723675814;
pc = ps + 1.0;
sf = (ps >= 0.0 ? (int) ps : (int) ps - 1) & -2;
cf = (pc >= 0.0 ? (int) pc : (int) pc - 1) & -2;
ps -= sf;
pc -= cf;
ps *= 2.0 - ps;
pc *= 2.0 - pc;
ps = ps * (-0.775 - 0.225 * ps) * ((sf & 2) - 1);
pc = pc * (-0.775 - 0.225 * pc) * ((cf & 2) - 1);
ax = (int)((lon * i_pi + 1.0) * width);
ay = (int)((qs + 1.0) * ry);
// // Hammer projection, not an inverse projection like we usually use
// z = 1.0 / Math.sqrt(1 + qc * NumberTools.cos(lon * 0.5));
// ax = (int)((qc * NumberTools.sin(lon * 0.5) * z + 1.0) * width);
// ay = (int)((qs * z + 1.0) * height * 0.5);
if(ax >= storedMap.width || ax < 0 || ay >= storedMap.height || ay < 0)
{
heightCodeData[x][y] = 1000;
continue;
}
if(storedMap.heightCodeData[ax][ay] >= 1000) // for the seam we get when looping around
{
ay = storedMap.wrapY(ax, ay);
ax = storedMap.wrapX(ax, ay);
}
xPositions[x][y] = pc * qc;
yPositions[x][y] = ps * qc;
zPositions[x][y] = qs;
heightData[x][y] = h = storedMap.heightData[ax][ay];
heightCodeData[x][y] = codeHeight(h);
heatData[x][y] = storedMap.heatData[ax][ay];
moistureData[x][y] = storedMap.moistureData[ax][ay];
minHeightActual = Math.min(minHeightActual, h);
maxHeightActual = Math.max(maxHeightActual, h);
}
minHeightActual = Math.min(minHeightActual, minHeight);
maxHeightActual = Math.max(maxHeightActual, maxHeight);
}
}
protected void regenerate(int startX, int startY, int usedWidth, int usedHeight,
double landMod, double coolMod, int stateA, int stateB)
{
if(cacheA != stateA || cacheB != stateB)// || landMod != storedMap.landModifier || coolMod != storedMap.coolingModifier)
{
storedMap.regenerate(0, 0, width << 1, height, landMod, coolMod, stateA, stateB);
minHeightActual = Double.POSITIVE_INFINITY;
maxHeightActual = Double.NEGATIVE_INFINITY;
minHeight = storedMap.minHeight;
maxHeight = storedMap.maxHeight;
minHeat0 = storedMap.minHeat0;
maxHeat0 = storedMap.maxHeat0;
minHeat1 = storedMap.minHeat1;
maxHeat1 = storedMap.maxHeat1;
minWet0 = storedMap.minWet0;
maxWet0 = storedMap.maxWet0;
minHeat = storedMap.minHeat;
maxHeat = storedMap.maxHeat;
minWet = storedMap.minWet;
maxWet = storedMap.maxWet;
cacheA = stateA;
cacheB = stateB;
}
setCenterLongitude(centerLongitude);
landData.refill(heightCodeData, 4, 999);
}
}
/**
* A concrete implementation of {@link WorldMapGenerator} that does no projection of the map, as if the area were
* completely flat or small enough that curvature is impossible to see. This also does not change heat levels at the
* far north and south regions of the map, since it is meant for areas that are all about the same heat level.
*/
public static class LocalMap extends WorldMapGenerator {
protected static final double terrainFreq = 1.45, terrainRidgedFreq = 3.1, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375;
//protected static final double terrainFreq = 1.65, terrainRidgedFreq = 1.8, heatFreq = 2.1, moistureFreq = 2.125, otherFreq = 3.375, riverRidgedFreq = 21.7;
protected double minHeat0 = Double.POSITIVE_INFINITY, maxHeat0 = Double.NEGATIVE_INFINITY,
minHeat1 = Double.POSITIVE_INFINITY, maxHeat1 = Double.NEGATIVE_INFINITY,
minWet0 = Double.POSITIVE_INFINITY, maxWet0 = Double.NEGATIVE_INFINITY;
public final Noise.Ridged2D terrain, otherRidged;
public final Noise.InverseLayered2D heat, moisture, terrainLayered;
public final double[][] xPositions,
yPositions,
zPositions;
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a
* 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to
* have significantly-exaggerated-in-size features while the equator is not distorted.
* Always makes a 256x128 map.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
* If you were using {@link LocalMap#LocalMap(long, int, int, Noise2D, double)}, then this would be the
* same as passing the parameters {@code 0x1337BABE1337D00DL, 256, 128, FastNoise.instance, 1.0}.
*/
public LocalMap() {
this(0x1337BABE1337D00DL, 256, 128, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a
* 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to
* have significantly-exaggerated-in-size features while the equator is not distorted.
* Takes only the width/height of the map. The initial seed is set to the same large long
* every time, and it's likely that you would set the seed when you call {@link #generate(long)}. The width and
* height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public LocalMap(int mapWidth, int mapHeight) {
this(0x1337BABE1337D00DL, mapWidth, mapHeight, FastNoise.instance,1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a
* 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to
* have significantly-exaggerated-in-size features while the equator is not distorted.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
*/
public LocalMap(long initialSeed, int mapWidth, int mapHeight) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a
* 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to
* have significantly-exaggerated-in-size features while the equator is not distorted.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses FastNoise as its noise generator, with the given octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public LocalMap(long initialSeed, int mapWidth, int mapHeight, double octaveMultiplier) {
this(initialSeed, mapWidth, mapHeight, FastNoise.instance, octaveMultiplier);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a
* 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to
* have significantly-exaggerated-in-size features while the equator is not distorted.
* Takes an initial seed and the width/height of the map. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact, but you can zoom in.
* Uses the given noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise}
*/
public LocalMap(long initialSeed, int mapWidth, int mapHeight, Noise2D noiseGenerator) {
this(initialSeed, mapWidth, mapHeight, noiseGenerator, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that can be used to wrap a sphere (as with a texture on a
* 3D model), with seamless east-west wrapping, no north-south wrapping, and distortion that causes the poles to
* have significantly-exaggerated-in-size features while the equator is not distorted.
* Takes an initial seed, the width/height of the map, and parameters for noise
* generation (a {@link Noise3D} implementation, which is usually {@link FastNoise#instance}, and a
* multiplier on how many octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers
* producing even more detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used,
* since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map
* cannot be changed after the fact, but you can zoom in. FastNoise will be the fastest 3D generator to use for
* {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the
* seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance}
* because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should
* probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on
* generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps
* that don't require zooming.
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param mapWidth the width of the map(s) to generate; cannot be changed later
* @param mapHeight the height of the map(s) to generate; cannot be changed later
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise#instance}
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public LocalMap(long initialSeed, int mapWidth, int mapHeight, Noise2D noiseGenerator, double octaveMultiplier) {
super(initialSeed, mapWidth, mapHeight);
xPositions = new double[width][height];
yPositions = new double[width][height];
zPositions = new double[width][height];
terrain = new Noise.Ridged2D(noiseGenerator, (int) (0.5 + octaveMultiplier * 10), terrainFreq);
terrainLayered = new Noise.InverseLayered2D(noiseGenerator, (int) (1 + octaveMultiplier * 6), terrainRidgedFreq * 0.325);
heat = new Noise.InverseLayered2D(noiseGenerator, (int) (0.5 + octaveMultiplier * 3), heatFreq, 0.75);
moisture = new Noise.InverseLayered2D(noiseGenerator, (int) (0.5 + octaveMultiplier * 4), moistureFreq, 0.55);
otherRidged = new Noise.Ridged2D(noiseGenerator, (int) (0.5 + octaveMultiplier * 6), otherFreq);
}
@Override
public int wrapY(final int x, final int y) {
return Math.max(0, Math.min(y, height - 1));
}
protected void regenerate(int startX, int startY, int usedWidth, int usedHeight,
double landMod, double coolMod, int stateA, int stateB)
{
boolean fresh = false;
if(cacheA != stateA || cacheB != stateB || landMod != landModifier || coolMod != coolingModifier)
{
minHeight = Double.POSITIVE_INFINITY;
maxHeight = Double.NEGATIVE_INFINITY;
minHeat0 = Double.POSITIVE_INFINITY;
maxHeat0 = Double.NEGATIVE_INFINITY;
minHeat1 = Double.POSITIVE_INFINITY;
maxHeat1 = Double.NEGATIVE_INFINITY;
minHeat = Double.POSITIVE_INFINITY;
maxHeat = Double.NEGATIVE_INFINITY;
minWet0 = Double.POSITIVE_INFINITY;
maxWet0 = Double.NEGATIVE_INFINITY;
minWet = Double.POSITIVE_INFINITY;
maxWet = Double.NEGATIVE_INFINITY;
cacheA = stateA;
cacheB = stateB;
fresh = true;
}
rng.setState(stateA, stateB);
long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong();
int t;
landModifier = (landMod <= 0) ? rng.nextDouble(0.29) + 0.91 : landMod;
coolingModifier = (coolMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : coolMod;
double p,
ps, pc,
qs, qc,
h, temp,
i_w = 1.0 / width, i_h = 1.0 / (height),
i_uw = usedWidth * i_w * i_w, i_uh = usedHeight * i_h * i_h, xPos, yPos = startY * i_h;
for (int y = 0; y < height; y++, yPos += i_uh) {
xPos = startX * i_w;
for (int x = 0, xt = 0; x < width; x++, xPos += i_uw) {
xPositions[x][y] = (xPos - .5) * 2.0;
yPositions[x][y] = (yPos - .5) * 2.0;
zPositions[x][y] = 0.0;
heightData[x][y] = (h = terrainLayered.getNoiseWithSeed(xPos +
terrain.getNoiseWithSeed(xPos, yPos, seedB - seedA) * 0.5,
yPos, seedA) + landModifier - 1.0);
heatData[x][y] = (p = heat.getNoiseWithSeed(xPos, yPos
+ otherRidged.getNoiseWithSeed(xPos, yPos, seedB + seedC),
seedB));
temp = otherRidged.getNoiseWithSeed(xPos, yPos, seedC + seedA);
moistureData[x][y] = (temp = moisture.getNoiseWithSeed(xPos - temp, yPos + temp, seedC));
minHeightActual = Math.min(minHeightActual, h);
maxHeightActual = Math.max(maxHeightActual, h);
if(fresh) {
minHeight = Math.min(minHeight, h);
maxHeight = Math.max(maxHeight, h);
minHeat0 = Math.min(minHeat0, p);
maxHeat0 = Math.max(maxHeat0, p);
minWet0 = Math.min(minWet0, temp);
maxWet0 = Math.max(maxWet0, temp);
}
}
minHeightActual = Math.min(minHeightActual, minHeight);
maxHeightActual = Math.max(maxHeightActual, maxHeight);
}
double heatDiff = 0.8 / (maxHeat0 - minHeat0),
wetDiff = 1.0 / (maxWet0 - minWet0),
hMod;
yPos = startY * i_h + i_uh;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++, yPos += i_uh) {
for (int x = 0; x < width; x++) {
h = heightData[x][y];
heightCodeData[x][y] = (t = codeHeight(h));
hMod = 1.0;
switch (t) {
case 0:
case 1:
case 2:
case 3:
h = 0.4;
hMod = 0.2;
break;
case 6:
h = -0.1 * (h - forestLower - 0.08);
break;
case 7:
h *= -0.25;
break;
case 8:
h *= -0.4;
break;
default:
h *= 0.05;
}
heatData[x][y] = (h = ((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6);
if (fresh) {
ps = Math.min(ps, h); //minHeat0
pc = Math.max(pc, h); //maxHeat0
}
}
}
if(fresh)
{
minHeat1 = ps;
maxHeat1 = pc;
}
heatDiff = coolingModifier / (maxHeat1 - minHeat1);
qs = Double.POSITIVE_INFINITY;
qc = Double.NEGATIVE_INFINITY;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff));
moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff);
if (fresh) {
qs = Math.min(qs, h);
qc = Math.max(qc, h);
ps = Math.min(ps, temp);
pc = Math.max(pc, temp);
}
}
}
if(fresh)
{
minHeat = qs;
maxHeat = qc;
minWet = ps;
maxWet = pc;
}
landData.refill(heightCodeData, 4, 999);
}
}
/**
* An unusual map generator that imitates an existing local map (such as a map of Australia, which it can do by
* default), without applying any projection or changing heat levels in the polar regions or equator.
*/
public static class LocalMimicMap extends LocalMap
{
public GreasedRegion earth;
public GreasedRegion shallow;
public GreasedRegion coast;
public GreasedRegion earthOriginal;
/**
* Constructs a concrete WorldMapGenerator for a map that should look like Australia, without projecting the
* land positions or changing heat by latitude. Always makes a 256x256 map.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
* If you were using {@link LocalMimicMap#LocalMimicMap(long, Noise2D, double)}, then this would be the
* same as passing the parameters {@code 0x1337BABE1337D00DL, FastNoise.instance, 1.0}.
*/
public LocalMimicMap() {
this(0x1337BABE1337D00DL
, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the
* given GreasedRegion's "on" cells, without projecting the land positions or changing heat by latitude.
* The initial seed is set to the same large long every time, and it's likely that you would set the seed when
* you call {@link #generate(long)}. The width and height of the map cannot be changed after the fact.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied
*/
public LocalMimicMap(GreasedRegion toMimic) {
this(0x1337BABE1337D00DL, toMimic, FastNoise.instance,1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the
* given GreasedRegion's "on" cells, without projecting the land positions or changing heat by latitude.
* Takes an initial seed and the GreasedRegion containing land positions. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact.
* Uses FastNoise as its noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied
*/
public LocalMimicMap(long initialSeed, GreasedRegion toMimic) {
this(initialSeed, toMimic, FastNoise.instance, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the
* given GreasedRegion's "on" cells, without projecting the land positions or changing heat by latitude.
* Takes an initial seed, the GreasedRegion containing land positions, and a multiplier that affects the level
* of detail by increasing or decreasing the number of octaves of noise used. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call {@link #generate(long)}.
* The width and height of the map cannot be changed after the fact.
* Uses FastNoise as its noise generator, with the given octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public LocalMimicMap(long initialSeed, GreasedRegion toMimic, double octaveMultiplier) {
this(initialSeed, toMimic, FastNoise.instance, octaveMultiplier);
}
/**
* Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the
* given GreasedRegion's "on" cells, without projecting the land positions or changing heat by latitude.
* Takes an initial seed, the GreasedRegion containing land positions, and parameters for noise generation (a
* {@link Noise3D} implementation, which is usually {@link FastNoise#instance}. The {@code initialSeed}
* parameter may or may not be used, since you can specify the seed to use when you call
* {@link #generate(long)}. The width and height of the map cannot be changed after the fact. Both FastNoise
* and FastNoise make sense to use for {@code noiseGenerator}, and the seed it's constructed with doesn't matter
* because this will change the seed several times at different scales of noise (it's fine to use the static
* {@link FastNoise#instance} or {@link FastNoise#instance} because they have no changing state between runs
* of the program). Uses the given noise generator, with 1.0 as the octave multiplier affecting detail.
*
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} or {@link FastNoise}
*/
public LocalMimicMap(long initialSeed, GreasedRegion toMimic, Noise2D noiseGenerator) {
this(initialSeed, toMimic, noiseGenerator, 1.0);
}
/**
* Constructs a concrete WorldMapGenerator for a map that should have land in roughly the same places as the
* given GreasedRegion's "on" cells, using an elliptical projection (specifically, a Mollweide projection).
* Takes an initial seed, the GreasedRegion containing land positions, parameters for noise generation (a
* {@link Noise3D} implementation, which is usually {@link FastNoise#instance}, and a multiplier on how many
* octaves of noise to use, with 1.0 being normal (high) detail and higher multipliers producing even more
* detailed noise when zoomed-in). The {@code initialSeed} parameter may or may not be used,
* since you can specify the seed to use when you call {@link #generate(long)}. The width and height of the map
* cannot be changed after the fact. FastNoise will be the fastest 3D generator to use for
* {@code noiseGenerator}, and the seed it's constructed with doesn't matter because this will change the
* seed several times at different scales of noise (it's fine to use the static {@link FastNoise#instance}
* because it has no changing state between runs of the program). The {@code octaveMultiplier} parameter should
* probably be no lower than 0.5, but can be arbitrarily high if you're willing to spend much more time on
* generating detail only noticeable at very high zoom; normally 1.0 is fine and may even be too high for maps
* that don't require zooming.
* @param initialSeed the seed for the GWTRNG this uses; this may also be set per-call to generate
* @param toMimic the world map to imitate, as a GreasedRegion with land as "on"; the height and width will be copied
* @param noiseGenerator an instance of a noise generator capable of 3D noise, usually {@link FastNoise} or {@link FastNoise}
* @param octaveMultiplier used to adjust the level of detail, with 0.5 at the bare-minimum detail and 1.0 normal
*/
public LocalMimicMap(long initialSeed, GreasedRegion toMimic, Noise2D noiseGenerator, double octaveMultiplier) {
super(initialSeed, toMimic.width, toMimic.height, noiseGenerator, octaveMultiplier);
earth = toMimic;
earthOriginal = earth.copy();
coast = earth.copy().not().fringe(2);
shallow = earth.copy().fringe(2);
}
/**
* Constructs a 256x256 unprojected local map that will use land forms with a similar shape to Australia.
* @param initialSeed
* @param noiseGenerator
* @param octaveMultiplier
*/
public LocalMimicMap(long initialSeed, Noise2D noiseGenerator, double octaveMultiplier)
{
this(initialSeed,
GreasedRegion.deserializeFromString(LZSPlus.decompress(
"ঢ堻\u0089⤠怹؈䊺䯼曵䟗温撞⎠ࣦಡ泡⠣乹ƹ畗♛Ղມ᳝⦱Ħ♮塳6Ð⸨劙⦤䤶氦ф䱾悽䐱⥑۴挬塉历㊳ኻǨ䀫⇆㭩䧏ᡃᮀ呣吰撤✽ڱ䪪䌥\u0E74䒉䂰䱁牪\u0D5Dᠠ\u2B69ร硘₤↖ଵ࠵ቬ䇏惒稊泈⇬㋖⼨੦䐳Ñթత汬ぺ皨ќ⟫恱椧㴈䥤❩ڔ坋悦±\u0FEEយ䭔嗈Ⓒ͆४ニ嬧柩ኅ娅ấ㞺ᑆиऩ䦙䥖审撣筬礐かྫྷüඈ䖮⡑ቀ䋋ᢐ吽卣£ឌ৪樬兾᳠䌡惇䉡䤐嘰㥈᧹᪐Ģ傢\u209Eईㅥ剭匼才吺ཤ筜啭್刂\u0D49\u1CAD䬒棡s㦉簬⌤爘\u08DAᥤௐ㙯ᱭ吝ό䑌\u3098ᒟ嵶ㇹ㲍➇∊獬⏬ዹ癳㍱㜸嚻㥂Ꮝㄇ㳹㜊\u16FBㅿ㚈䕿\u2B76琧笙䑘瘄打≓坻Ȏ䣪\u0D5B礭⿹ೇ凛㵗\u0FE8ॽᤵন幯\u0FE6䱚懌煚睆Ἑ䊩ࣰᒐ燞盇獹亪咑ḟ⽔\u0E80乞悐箆䨎罘呮ॉ懜ὗᢥ炑ϳ㵏\u1DEA擕⍖ኗ☻ಽ斳Ǔկ仡㿣ₑ䨅┄嵋孨ဤӰ\u1AAF捺Α᰿⡻؎Ⴔ⠂ᆱ\u05CC愦aҠ翪⦄ᐥ籀瀍Ƞ牦粤Ṯ¶㼪棙ᖠ哧㧢̰溣秄ከၮォ\u0600栿䣔\u0B4F禆ैə〰䊢ጽ؛㪏ㇸ拂⋘挵\u058E⬺榈ᴺ⏘墲搘咿ᢀ溠碘碰匤夌君Ⲽ挘嬹◎\u1C4Aጔ䒰ጬ䶬㊌\u1CBF卄暴䂼㖭ਃ\u1ABFሬ䲋ΩƢґⶭ敾⤀版ᬥ㍼涾㍿ূ㎘妸卑㪥ௌ榱刭㹍௴箷㒔究猌牫劂䩲氪䙷䫧⇪ᄦɦᄶ↧↱⸌煎ቸ⭒䈤灌ڧ\u208F㚮…Ӣ椒⎲\u2B92䑸䡫㵰წἭ炊㭼櫢汿㏺䮳㊶棢ᯢ屰䇍ৣ㡆٣䣾⺦䗺戲歪売孬༣檔ᐰ\u1A5F㎬㋂ ⧪①ત嘿㮬⇢㧚堣ሆ粢ᇚ䬮创ⳤ⌼俾℞䉥⒡䌼᭗ᛁ㷶⢡⥧ݳ㯡婏㰚枤筱礯慚㶬ᆤ\u0C5Aギᵦ⥾⢡ᢙࡕŞᏡㅠᕓسூ屍ᚪ曩䝙⟙傽᳛ᒌ㍱揢楊噯熣Ӷⷆ文\u1ABA䝧䐅宎摶痋ᒄ㔾堲⭙値カ縛⚵䑮㫦旸㉀䴔⥛㥂␃墑丳夐壍ウ旫∞ᯒ㡝傸娴珐ཽ䍴܌⋂წ↬♴建机ᶧ成您唶ⅎ\u312Eㆮ堥歓䗏ᔴㆣᡗㅼᑙ䵱榓姫滜煾澱窴\u1AC1噀Γ㤜֖\u1C9C㡓⼘翔ু㍥亁殟⬜柹溅礙仺ཟ爎䄋\u31EA∰庺ጓ㡷捎⇷徵徺挒榷斓⩁䜢ỳ娙\u1F4F儙ޏ垓溻拍῾羿\u1F4Eᰗ澻掷ܲ⒮㖯⪅瀝઼䐭䪨⩑嫓ཤ㉚倸殅盲ຜѭᕱ䄥稽捻㦡՞Ѐ缣䤝䐝爺䢐㤦ₒञᐷ繈冶䄬䓊䞨玩嗬ޣ硃玕㐆䮆劁\u0B64ڂέҼ\u0588【您ࠄ©\u3101ແㄹȲḨ剌㈘ኀ懈䞞⊫Ω\u0CD8唱ᡊɈ掯䧅ݢԼ公ព汽䕄礵凈⽢剚拜ྀv䊨㓱䊓˄✻䉋#ῶ㱛侥椿䛘亗⌢狦䛊䛮 ᅅ処Ꮑ䩍ᅸ塭壍ᗴᔮ僂ɭᜮᏢ㢩ూ掠㵶\u0871瓼璿履ᕁ秥ᄋ彅⩠Щ㒠焏Éʱ䤮\u2E6Cİ㈌ದ⪗寤兪\u082Fƀ⤢ঌ歈眶Ң䯲帨ᄄ⓮屿䖨⅍灉ᒑ嚤絢⢆\u0A63䮀刪祔䯹⺿ᩒᑃ嵵且㵥ӋᎠ\u08A1⠽䢠᮸ㅜ杬\u20BE䧒冧㽌䗐掳碌䃀㑤\u0B04ᯰ䝚淍ỉ\u2066熀䔨䋥␥ࡋ榳ᵡ㽒獝下㑆ࠐ積ケૄ抺䦐師紺妨乩䑘Ⴗᠬ㜨଼ᖿ碟Ղ翰怠⊢悙䈸㏭㌞炒䙳ᥲᶭ儷痑判⣀哲瓅睖⧮⪼禡潴ㇴ\u1DEA幵厮䎴ઢ䠡曖亿壑ᤳᣣ抑嘄࿋ᨚّ\u0883⾼丰ㆆ嗽፡၆Ꮒ॔\u20FA磤䰪䲕ጢ瘊ޢ\u1755䍆ぇ宷欱\u0089啔爡㋄ଁ喒巪˪ჺ悐䆜đじ䚦ಎУኃ泴ƅᡰ懎㮰⺡⊆⊢㔁\u171C瀓\u2456ᄘᄃ玗⥨溉䩁➴㮿怵\u2BC8ᭆ焃♸\u10C8Ȩ䭦⊚⦭仼倴ઙ樤ᓳ䎤ἠᩌᇡ捷\u0099甡䢴Ȩጹ獰⟸はᮺᏤ㞁и滏磀唡竱᪀⚨ʗ猔ܥ㐶䇈珰䋵【ȯूῐਣᒒ畐撆ഘᨫ兡ߑ奈瀾⎛ⵖ歘੦偺\u0080儢悝䬪ǹ炈ኡ稴䁄༡㺞「仂悴ล∽璥崬爰ಘ㿵懯㉰翌↶\u08D1嫊⏇㰢氲砘㾠瀤䏡㢄း惛䣡\u2BE2ခవ䌸͒⼏倨忈፨氭\u2FEE区\u0BE1䵚愔\u242B捛吊₌仆湁嗰⇄ῑ㐩折ດ倪<\u1ACE炳⌳᜴刜ᔖ娸䎶ڤ㉥⇰ଭ㸲᎐井ߘ儃桓ϐ⇴䑙๊䙍烏Ɖ琳\u1DEAসᨡေ斢⠯䐰樧ັ㋅ҍᙨ獎ʰ壅䡃旔⡋糈戩㘬\u0EE7䁍孚〥⥹畱烚傊榇傟◀ኁ䄍䗃⦦ɕ啴磻ᮋ秈䈨⃦ĩ䞪Ỉぇ⫽⦥甧右⛅㌜ガ䊜淚ཁ㳧䋘⾼䒀♺朠樒̬͛愱㔴想䊽䐢嬀惉ધ䎃㚼ᓾ㣾昍㮬寝玥涿Ả㽜Ѳᩬឰњ壍च⺱涥㍸丸秆⓼姁⪾Ԟ丿牢ỡ瞻⡕\u0CD1咊ߐ⠣吣惢ॠ䇺ള癇喌⣉碝⥪䵥\u2452㐉玀Żツٛ剌㾇⁼秌皔䴕໙昆㹅恌\u1ACE縆冸㦡樲峫柆ⲅ緊\u0E79ᑳ粤禐㱵愋์禐ɼ䮎穂啈墓竘䀣㫷䯹⋪䍥級〚᷈瞚ؕํ㍖⢽㚐䵖璍䓜\u2E3C氓Ḿ塱ኋ宱㮴㚑㫃\u2BC0⌙瞅秿烋巳敟Τ縖⬬侔䶙☥Ⅴẉ杷ю漭䠛乖㍒婔᷃ไ⻄ᬶ䰴⇚玥∫䐋崫ガ䞲澍⍂᎙䘛⨭疹㗖Ի⦐⒚倁缹秬瀎ा潮恜Ḗݗ獭捾ēૡ䫳喛ോѺ⳿ᖘଦ纠\u08D3ɱ燭濄ㆱ㇗仔\u07B9咫簾笨౾с⧀獜䩦宵த⛉挣㴟\u1F46翖▇梪द㳔礝⾀⬣犹棠ᢞ愵ㅁᅌ揬牧㐷㧈֞攤绊磿潔吨咹ᢆ䋰⫇ⱛᘐẊ伥⿒䆇殡✗Ȫ䡫䣨䆣ᠧ䄩ؖⱧ傗攔Ⲩ展㱉璟\u2434乁⌣\u4DB6¦濱巐䦤ᨢ䎠ఠࡉê䱄矝污ր➡\u08C2䈨᭤狆柝䰩ׁ݄⠺甝柁㈫≑⇉⼺紫恷၅ь⬪㕅ᒝঁ癵䛒昌ᓈ婱䪠想䎖并㲷ᔎ➪ࣷ₂ᕜṼ珇ⷜ䴡䗖ᛧ弻ℒζ哖ᆹⲺन笕ૃ⡷⫝̸⻖≍F\u0CFC݃硰熓\u0AD8㮰Ἰ䀲ǜ\u0EE4温点\u0BD8௦咺灧␊㘨渺㴗⩐暮㫋⌔ฎ儕ၨ↣⸜捄ཕ㦿䴬ڢ䛝䄔ช溭㫑洠⛔ः࿙ᥙ䕬ᄦ\u2455帚\u1AB2如嫓ᄮ惰̗ጳ焉䧌㦀倢堪ᅾ\u0530㣄䐰ڌ缡瀨⸗榨ⷥṓၸñ㉆䝛壒懨⫱ᒑ㡍漪㉰Ⱡ慚॔狡䴵〾\u0B3A䋑᷒壆䅸姆\u1CA0唞Ñ䩔䌷疧拤犒†䵛⋊氣液䈘˚ѕ㠡ᅐ䠰紦睐䁗ɠ猠ろቨ૩䜣㉫\u13F9ᗩ†"
)),
noiseGenerator, octaveMultiplier);
}
protected void regenerate(int startX, int startY, int usedWidth, int usedHeight,
double landMod, double coolMod, int stateA, int stateB)
{
boolean fresh = false;
if(cacheA != stateA || cacheB != stateB || landMod != landModifier || coolMod != coolingModifier)
{
minHeight = Double.POSITIVE_INFINITY;
maxHeight = Double.NEGATIVE_INFINITY;
minHeat0 = Double.POSITIVE_INFINITY;
maxHeat0 = Double.NEGATIVE_INFINITY;
minHeat1 = Double.POSITIVE_INFINITY;
maxHeat1 = Double.NEGATIVE_INFINITY;
minHeat = Double.POSITIVE_INFINITY;
maxHeat = Double.NEGATIVE_INFINITY;
minWet0 = Double.POSITIVE_INFINITY;
maxWet0 = Double.NEGATIVE_INFINITY;
minWet = Double.POSITIVE_INFINITY;
maxWet = Double.NEGATIVE_INFINITY;
cacheA = stateA;
cacheB = stateB;
fresh = true;
}
rng.setState(stateA, stateB);
long seedA = rng.nextLong(), seedB = rng.nextLong(), seedC = rng.nextLong();
int t;
landModifier = (landMod <= 0) ? rng.nextDouble(0.29) + 0.91 : landMod;
coolingModifier = (coolMod <= 0) ? rng.nextDouble(0.45) * (rng.nextDouble()-0.5) + 1.1 : coolMod;
earth.remake(earthOriginal);
if(zoom > 0)
{
int stx = Math.min(Math.max((zoomStartX - (width >> 1)) / ((2 << zoom) - 2), 0), width ),
sty = Math.min(Math.max((zoomStartY - (height >> 1)) / ((2 << zoom) - 2), 0), height);
for (int z = 0; z < zoom; z++) {
earth.zoom(stx, sty).expand8way().fray(0.5).expand();
}
coast.remake(earth).not().fringe(2 << zoom).expand().fray(0.5);
shallow.remake(earth).fringe(2 << zoom).expand().fray(0.5);
}
else
{
coast.remake(earth).not().fringe(2);
shallow.remake(earth).fringe(2);
}
double p,
ps, pc,
qs, qc,
h, temp,
i_w = 1.0 / width, i_h = 1.0 / (height),
i_uw = usedWidth * i_w * i_w, i_uh = usedHeight * i_h * i_h, xPos, yPos = startY * i_h;
for (int y = 0; y < height; y++, yPos += i_uh) {
xPos = startX * i_w;
for (int x = 0, xt = 0; x < width; x++, xPos += i_uw) {
xPositions[x][y] = (xPos - .5) * 2.0;
yPositions[x][y] = (yPos - .5) * 2.0;
zPositions[x][y] = 0.0;
if(earth.contains(x, y))
{
h = NumberTools.swayTight(terrainLayered.getNoiseWithSeed(xPos +
terrain.getNoiseWithSeed(xPos, yPos, seedB - seedA) * 0.5,
yPos, seedA)) * 0.85;
if(coast.contains(x, y))
h += 0.05;
else
h += 0.15;
}
else
{
h = NumberTools.swayTight(terrainLayered.getNoiseWithSeed(xPos +
terrain.getNoiseWithSeed(xPos, yPos, seedB - seedA) * 0.5,
yPos, seedA)) * -0.9;
if(shallow.contains(x, y))
h = (h - 0.08) * 0.375;
else
h = (h - 0.125) * 0.75;
}
//h += landModifier - 1.0;
heightData[x][y] = h;
heatData[x][y] = (p = heat.getNoiseWithSeed(xPos, yPos
+ otherRidged.getNoiseWithSeed(xPos, yPos, seedB + seedC),
seedB));
temp = otherRidged.getNoiseWithSeed(xPos, yPos, seedC + seedA);
moistureData[x][y] = (temp = moisture.getNoiseWithSeed(xPos - temp, yPos + temp, seedC));
minHeightActual = Math.min(minHeightActual, h);
maxHeightActual = Math.max(maxHeightActual, h);
if(fresh) {
minHeight = Math.min(minHeight, h);
maxHeight = Math.max(maxHeight, h);
minHeat0 = Math.min(minHeat0, p);
maxHeat0 = Math.max(maxHeat0, p);
minWet0 = Math.min(minWet0, temp);
maxWet0 = Math.max(maxWet0, temp);
}
}
minHeightActual = Math.min(minHeightActual, minHeight);
maxHeightActual = Math.max(maxHeightActual, maxHeight);
}
double heatDiff = 0.8 / (maxHeat0 - minHeat0),
wetDiff = 1.0 / (maxWet0 - minWet0),
hMod;
yPos = startY * i_h + i_uh;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++, yPos += i_uh) {
for (int x = 0; x < width; x++) {
h = heightData[x][y];
heightCodeData[x][y] = (t = codeHeight(h));
hMod = 1.0;
switch (t) {
case 0:
case 1:
case 2:
case 3:
h = 0.4;
hMod = 0.2;
break;
case 6:
h = -0.1 * (h - forestLower - 0.08);
break;
case 7:
h *= -0.25;
break;
case 8:
h *= -0.4;
break;
default:
h *= 0.05;
}
heatData[x][y] = (h = ((heatData[x][y] - minHeat0) * heatDiff * hMod) + h + 0.6);
if (fresh) {
ps = Math.min(ps, h); //minHeat0
pc = Math.max(pc, h); //maxHeat0
}
}
}
if(fresh)
{
minHeat1 = ps;
maxHeat1 = pc;
}
heatDiff = coolingModifier / (maxHeat1 - minHeat1);
qs = Double.POSITIVE_INFINITY;
qc = Double.NEGATIVE_INFINITY;
ps = Double.POSITIVE_INFINITY;
pc = Double.NEGATIVE_INFINITY;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
heatData[x][y] = (h = ((heatData[x][y] - minHeat1) * heatDiff));
moistureData[x][y] = (temp = (moistureData[x][y] - minWet0) * wetDiff);
if (fresh) {
qs = Math.min(qs, h);
qc = Math.max(qc, h);
ps = Math.min(ps, temp);
pc = Math.max(pc, temp);
}
}
}
if(fresh)
{
minHeat = qs;
maxHeat = qc;
minWet = ps;
maxWet = pc;
}
landData.refill(heightCodeData, 4, 999);
}
}
}