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}