001package squidpony.squidgrid.mapping; 002 003import squidpony.annotation.Beta; 004import squidpony.squidmath.Coord; 005import squidpony.squidmath.SeededNoise; 006import squidpony.squidmath.StatefulRNG; 007 008import java.util.ArrayList; 009import java.util.List; 010 011/** 012 * A map generation factory using Perlin noise to make island chain style maps. 013 * <br> 014 * Based largely on work done by Metsa from #rgrd . This is marked Beta, despite having been in SquidLib for years, 015 * because it could still be modified as a simpler substitute for {@link WorldMapGenerator}. WorldMapGenerator does tend 016 * to produce much higher quality in its maps, however, due to calculating heat and moisture levels as well as height, 017 * then using those to build blending biomes. 018 * 019 * @author Eben Howard - http://squidpony.com - howard@squidpony.com 020 */ 021@Beta 022public class MetsaMapFactory { 023 //HEIGHT LIMITS 024 025 public static final double SEA_LEVEL = 0, 026 BEACH_LEVEL = 0.15, 027 PLAINS_LEVEL = 0.5, 028 MOUNTAIN_LEVEL = 0.73, 029 SNOW_LEVEL = 0.95, 030 DEEP_SEA_LEVEL = -0.1; 031 032 //BIOMESTUFF 033 private final double POLAR_LIMIT = 0.65, DESERT_LIMIT = 0.15; 034 035 //SHADOW 036 private final double SHADOW_LIMIT = 0.01; 037//COLORORDER 038/* 039 0 = deepsea 040 1 = beach 041 2 = low 042 3 = high 043 4 = mountain 044 5 = snowcap 045 6 = lowsea 046 */ 047// new SColor[]{SColor.DARK_SLATE_GRAY, SColor.SCHOOL_BUS_YELLOW, SColor.YELLOW_GREEN, 048// SColor.GREEN_BAMBOO, SColorFactory.lighter(SColor.LIGHT_BLUE_SILK), SColor.ALICE_BLUE, SColor.AZUL}; 049// new SColor[]{SColor.DARK_SLATE_GRAY, SColor.SCHOOL_BUS_YELLOW, SColor.YELLOW_GREEN, 050// SColor.GREEN_BAMBOO, SColorFactory.lighter(SColor.LIGHT_BLUE_SILK), SColor.ALICE_BLUE, SColor.AZUL}; 051 052 private int width; 053 private int height; 054 private int CITYAMOUNT = 14; 055 056 private List<Coord> cities = new ArrayList<>(); 057 private StatefulRNG rng; 058 private double maxPeak; 059 private double[][] map; 060 public MetsaMapFactory() 061 { 062 this(240, 120, new StatefulRNG()); 063 } 064 public MetsaMapFactory(int width, int height) 065 { 066 this(width, height, new StatefulRNG()); 067 } 068 public MetsaMapFactory(int width, int height, long rngSeed) 069 { 070 this(width, height, new StatefulRNG(rngSeed)); 071 } 072 073 public MetsaMapFactory(int width, int height, StatefulRNG rng) 074 { 075 this.rng = rng; 076 this.width = width; 077 this.height = height; 078 map = makeHeightMap(); 079 } 080 081 public int getShadow(int x, int y, double[][] map) { 082 if (x >= width - 1 || y <= 0) { 083 return 0; 084 } 085 double upRight = map[x + 1][y - 1]; 086 double right = map[x + 1][y]; 087 double up = map[x][y - 1]; 088 double cur = map[x][y]; 089 if (cur <= 0) { 090 return 0; 091 } 092 double slope = cur - (upRight + up + right) / 3; 093 if (slope < SHADOW_LIMIT && slope > -SHADOW_LIMIT) { 094 return 0; 095 } 096 if (slope >= SHADOW_LIMIT) { 097 return -1; //"alpha" 098 } 099 if (slope <= -SHADOW_LIMIT) { 100 return 1; 101 } else { 102 return 0; 103 } 104 } 105 106 /** 107 * Finds and returns the closest point containing a city to the given point. 108 * Does not include provided point as a possible city location. 109 * 110 * If there are no cities, null is returned. 111 * 112 * @param point 113 * @return 114 */ 115 public Coord closestCity(Coord point) { 116 double dist = 999999999, newdist; 117 Coord closest = null; 118 for (Coord c : cities) { 119 if (c.equals(point)) { 120 continue;//skip the one being tested for 121 } 122 newdist = point.distanceSq(c); 123 if (newdist < dist) { 124 dist = newdist; 125 closest = c; 126 } 127 } 128 return closest; 129 } 130 131 public double[][] makeHeightMap() { 132 double[][] map = HeightMapFactory.heightMap(width, height, rng.nextLong()); 133 134 for (int x = 0; x < width / 8; x++) { 135 for (int y = 0; y < height; y++) { 136 map[x][y] = map[x][y] - 1.0 + x / ((width - 1) * 0.125); 137 if (map[x][y] > maxPeak) { 138 maxPeak = map[x][y]; 139 } 140 } 141 } 142 143 for (int x = width / 8; x < 7 * width / 8; x++) { 144 for (int y = 0; y < height; y++) { 145 map[x][y] = map[x][y]; 146 if (map[x][y] > maxPeak) { 147 maxPeak = map[x][y]; 148 } 149 } 150 } 151 152 for (int x = 7 * width / 8; x < width; x++) { 153 for (int y = 0; y < height; y++) { 154 map[x][y] = map[x][y] - 1.0 + (width - 1 - x) / ((width - 1) * 0.125); 155 if (map[x][y] > maxPeak) { 156 maxPeak = map[x][y]; 157 } 158 } 159 } 160 161 return map; 162 } 163 164 public void regenerateHeightMap() 165 { 166 map = makeHeightMap(); 167 } 168 public void regenerateHeightMap(int width, int height) 169 { 170 this.width = width; 171 this.height = height; 172 map = makeHeightMap(); 173 cities.clear(); 174 } 175 176 public int[][] makeBiomeMap() { 177 //biomes 0 normal 1 snow 178 int[][] biomeMap = new int[width][height]; 179 for (int x = 0; x < width; x++) { 180 for (int y = 0; y < height; y++) { 181 biomeMap[x][y] = 0; 182 double distanceFromEquator = Math.abs(y - height * 0.5) / (height * 0.5); 183 distanceFromEquator += SeededNoise.noise(x * 0.0073, y * 0.0073, 123456789) / 8 + map[x][y] / 32; 184 if (distanceFromEquator > POLAR_LIMIT) { 185 biomeMap[x][y] = 1; 186 } 187 if (distanceFromEquator < DESERT_LIMIT) { 188 biomeMap[x][y] = 2; 189 } 190 if (distanceFromEquator > POLAR_LIMIT + 0.25) { 191 biomeMap[x][y] = 3; 192 } 193 } 194 } 195 return biomeMap; 196 } 197 198 public int[][] makeNationMap() { 199 // nationmap, 4 times less accurate map used for nations -1 no nation 200 int[][] nationMap = new int[width][height]; 201 for (int i = 0; i < width / 4; i++) { 202 for (int j = 0; j < height / 4; j++) { 203 if (map[i * 4][j * 4] < 0) { 204 nationMap[i][j] = -1; 205 } else { 206 nationMap[i][j] = 0; 207 } 208 } 209 } 210 return nationMap; 211 } 212 213 public double[][] makeWeightedMap() { 214 //Weighted map for road 215 double[][] weightedMap = new double[width][height]; 216 double SEALEVEL = 0; 217 double BEACHLEVEL = 0.05; 218 double PLAINSLEVEL = 0.3; 219 for (int i = 0; i < width / 4; i++) { 220 for (int j = 0; j < height / 4; j++) { 221 weightedMap[i][j] = 0; 222 if (map[i * 4][j * 4] > BEACHLEVEL) { 223 weightedMap[i][j] = 2 + (map[i * 4][j * 4] - PLAINSLEVEL) * 8; 224 } 225 if (map[i][j] <= BEACHLEVEL && map[i * 4][j * 4] >= SEALEVEL) { 226 weightedMap[i][j] = 2 - map[i * 4][j * 4] * 2; 227 } 228 } 229 } 230 231 CITIES: 232 for (int i = 0; i < CITYAMOUNT; i++) { 233 int px = rng.between(0, width), py = rng.between(0, height), frustration = 0; 234 while (map[px][py] < SEALEVEL || map[px][py] > BEACHLEVEL) { 235 px = rng.between(0, width); 236 py = rng.between(0, height); 237 if(frustration++ > 20) 238 continue CITIES; 239 } 240 cities.add(Coord.get(4 * (px >> 2), 4 * (py >> 2))); 241 } 242 return weightedMap; 243 } 244 245 public List<Coord> getCities() { 246 return cities; 247 } 248 249 public double getMaxPeak() { 250 return maxPeak; 251 } 252 253 public double[][] getHeightMap() { 254 return map; 255 } 256 257 public int getHeight() { 258 return height; 259 } 260 261 public int getWidth() { 262 return width; 263 } 264}