001package squidpony.squidgrid.mapping; 002 003import squidpony.squidmath.*; 004 005import java.util.ArrayList; 006import java.util.List; 007 008/** 009 * Map generator using Perlin/Simplex noise for the formation of "rooms" and then WobblyLine to connect with corridors. 010 * Created by Tommy Ettinger on 4/18/2016. 011 */ 012public class OrganicMapGenerator { 013 public char[][] map; 014 public int[][] environment; 015 public RNG rng; 016 protected int width, height; 017 public double noiseMin, noiseMax; 018 public OrganicMapGenerator() 019 { 020 this(0.55, 0.65, 80, 30, new RNG()); 021 } 022 public OrganicMapGenerator(int width, int height) 023 { 024 this(0.55, 0.65, width, height, new RNG()); 025 } 026 public OrganicMapGenerator(int width, int height, RNG rng) 027 { 028 this(0.55, 0.65, width, height, rng); 029 } 030 public OrganicMapGenerator(double noiseMin, double noiseMax, int width, int height, RNG rng) 031 { 032 this.rng = rng; 033 this.width = Math.max(3, width); 034 this.height = Math.max(3, height); 035 this.noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin)); 036 this.noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax)); 037 map = new char[this.width][this.height]; 038 environment = new int[this.width][this.height]; 039 } 040 041 /** 042 * Generate a map as a 2D char array using the width and height specified in the constructor. 043 * Should produce an organic, cave-like map. 044 * @return a 2D char array for the map that should be organic-looking. 045 */ 046 public char[][] generate() 047 { 048 double shift, shift2, temp; 049 boolean[][] working = new boolean[width][height], blocks = new boolean[8][8]; 050 int ctr = 0, frustration = 0; 051 REDO: 052 while (frustration < 10) { 053 shift = rng.nextDouble(2048); 054 shift2 = rng.between(4096, 8192); 055 ctr = 0; 056 for (int x = 0; x < width; x++) { 057 for (int y = 0; y < height; y++) { 058 map[x][y] = '#'; 059 temp = (PerlinNoise.noise(x / 1.7 + shift, y / 1.7 + shift) * 5 060 + PerlinNoise.noise(x / 0.7 + shift2, y / 0.7 + shift2) * 3) / 8.0; 061 if (temp >= noiseMin && temp <= noiseMax) { 062 working[x][y] = true; 063 ctr++; 064 blocks[x * 8 / width][y * 8 / height] = true; 065 } 066 else 067 { 068 working[x][y] = false; 069 } 070 } 071 } 072 for (int x = 0; x < 8; x++) { 073 for (int y = 0; y < 8; y++) { 074 if (!blocks[x][y]) { 075 frustration++; 076 ctr = 0; 077 continue REDO; 078 } 079 } 080 } 081 break; 082 } 083 if(ctr < (width + height) * 3 || frustration >= 10) { 084 noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin - 0.05)); 085 noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax + 0.05)); 086 return generate(); 087 } 088 ctr = 0; 089 ArrayList<short[]> allRegions = CoordPacker.split(CoordPacker.pack(working)), 090 regions = new ArrayList<>(allRegions.size()); 091 short[] region, linking, tempPacked; 092 List<Coord> path, path2; 093 Coord start, end; 094 char[][] t; 095 for (short[] r : allRegions) { 096 if (CoordPacker.count(r) > 5) { 097 region = CoordPacker.expand(r, 1, width, height, false); 098 if(CoordPacker.isEmpty(region)) 099 continue; 100 regions.add(region); 101 ctr += CoordPacker.count(region); 102 tempPacked = CoordPacker.negatePacked(region); 103 map = CoordPacker.mask(map, tempPacked, '.'); 104 } 105 } 106 int oldSize = regions.size(); 107 if(oldSize < 4 || ctr < (width + height) * 3) { 108 noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin - 0.05)); 109 noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax + 0.05)); 110 return generate(); 111 } 112 113 for (int x = 0; x < width; x++) { 114 for (int y = 0; y < height; y++) { 115 environment[x][y] = (map[x][y] == '.') ? MixedGenerator.CAVE_FLOOR : MixedGenerator.CAVE_WALL; 116 } 117 } 118 tempPacked = CoordPacker.pack(map, '.'); 119 int tick = 1; 120 regions = rng.shuffle(regions); 121 while (regions.size() > 1) 122 { 123 124 region = regions.remove(0); 125 /* 126 tick = (tick + 1) % 5; 127 if(tick == 0) { 128 ctr -= CoordPacker.count(region); 129 continue; 130 }*/ 131 linking = regions.get(0); 132 start = CoordPacker.singleRandom(region, rng); 133 end = CoordPacker.singleRandom(linking, rng); 134 path = WobblyLine.line(start.x, start.y, end.x, end.y, width, height, 0.75, rng); 135 for(Coord elem : path) 136 { 137 if(elem.x < width && elem.y < height) { 138 if (map[elem.x][elem.y] == '#') { 139 map[elem.x][elem.y] = '.'; 140 environment[elem.x][elem.y] = MixedGenerator.CORRIDOR_FLOOR; 141 ctr++; 142 } /*else if (rng.nextBoolean() && 143 CoordPacker.queryPacked(CoordPacker.differencePacked(tempPacked, region), elem.x, elem.y)) { 144 linking = regions.get(rng.nextInt(regions.size())); 145 start = elem; 146 end = CoordPacker.singleRandom(linking, rng); 147 path2 = WobblyLine.line(start.x, start.y, end.x, end.y, width, height, 0.75, rng); 148 for(Coord elem2 : path2) 149 { 150 if(elem2.x < width && elem2.y < height) { 151 if (map[elem2.x][elem2.y] == '#') { 152 map[elem2.x][elem2.y] = '.'; 153 environment[elem2.x][elem2.y] = MixedGenerator.CORRIDOR_FLOOR; 154 ctr++; 155 } 156 } 157 } 158 break; 159 }*/ 160 } 161 } 162 } 163 int upperY = height - 1; 164 int upperX = width - 1; 165 for (int i = 0; i < width; i++) { 166 map[i][0] = '#'; 167 map[i][upperY] = '#'; 168 environment[i][0] = MixedGenerator.UNTOUCHED; 169 environment[i][upperY] = MixedGenerator.UNTOUCHED; 170 } 171 for (int i = 0; i < height; i++) { 172 map[0][i] = '#'; 173 map[upperX][i] = '#'; 174 environment[0][i] = MixedGenerator.UNTOUCHED; 175 environment[upperX][i] = MixedGenerator.UNTOUCHED; 176 } 177 178 if(ctr < (width + height) * 3) { 179 noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin - 0.05)); 180 noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax + 0.05)); 181 return generate(); 182 } 183 return map; 184 } 185 186 187 /** 188 * Gets a 2D array of int constants, each representing a type of environment corresponding to a static field of 189 * MixedGenerator. This array will have the same size as the last char 2D array produced by generate(); the value 190 * of this method if called before generate() is undefined, but probably will be a 2D array of all 0 (UNTOUCHED). 191 * <ul> 192 * <li>MixedGenerator.UNTOUCHED, equal to 0, is used for any cells that aren't near a floor.</li> 193 * <li>MixedGenerator.ROOM_FLOOR, equal to 1, is used for floor cells inside wide room areas.</li> 194 * <li>MixedGenerator.ROOM_WALL, equal to 2, is used for wall cells around wide room areas.</li> 195 * <li>MixedGenerator.CAVE_FLOOR, equal to 3, is used for floor cells inside rough cave areas.</li> 196 * <li>MixedGenerator.CAVE_WALL, equal to 4, is used for wall cells around rough cave areas.</li> 197 * <li>MixedGenerator.CORRIDOR_FLOOR, equal to 5, is used for floor cells inside narrow corridor areas.</li> 198 * <li>MixedGenerator.CORRIDOR_WALL, equal to 6, is used for wall cells around narrow corridor areas.</li> 199 * </ul> 200 * @return a 2D int array where each element is an environment type constant in MixedGenerator 201 */ 202 public int[][] getEnvironment() 203 { 204 return environment; 205 } 206}