001package squidpony.squidgrid.mapping;
002
003import squidpony.annotation.Beta;
004import squidpony.squidmath.Coord;
005import squidpony.squidmath.LightRNG;
006import squidpony.squidmath.PerlinNoise;
007import squidpony.squidmath.StatefulRNG;
008
009import java.util.LinkedList;
010import java.util.List;
011
012import static java.lang.Math.round;
013
014/**
015 * A map generation factory using perlin noise to make island chain style maps.
016 *
017 * Based largely on work done by Metsa from #rgrd
018 *
019 * @author Eben Howard - http://squidpony.com - howard@squidpony.com
020 */
021@Beta
022public class MetsaMapFactory {
023    //HEIGHT LIMITS
024
025    //BIOMESTUFF
026    private final double POLARLIMIT = 0.5, DESERTLIMIT = 0.1;
027
028    //SHADOW
029    private final double SHADOWLIMIT = 0.01;
030//COLORORDER
031/*
032     0 = deepsea
033     1 = beach
034     2 = low
035     3 = high
036     4 = mountain
037     5 = snowcap
038     6 = lowsea
039     */
040//            new SColor[]{SColor.DARK_SLATE_GRAY, SColor.SCHOOL_BUS_YELLOW, SColor.YELLOW_GREEN,
041//        SColor.GREEN_BAMBOO, SColorFactory.lighter(SColor.LIGHT_BLUE_SILK), SColor.ALICE_BLUE, SColor.AZUL};
042//            new SColor[]{SColor.DARK_SLATE_GRAY, SColor.SCHOOL_BUS_YELLOW, SColor.YELLOW_GREEN,
043//        SColor.GREEN_BAMBOO, SColorFactory.lighter(SColor.LIGHT_BLUE_SILK), SColor.ALICE_BLUE, SColor.AZUL};
044
045    private int width;
046    private int height;
047    private int CITYAMOUNT = 14;
048
049    private List<Coord> cities = new LinkedList<>();
050    private StatefulRNG rng;
051    private double maxPeak = 0;
052
053    public MetsaMapFactory()
054    {
055        rng = new StatefulRNG();
056        width = 1000;
057        height = 600;
058    }
059    public MetsaMapFactory(int width, int height)
060    {
061        rng = new StatefulRNG();
062        this.width = width;
063        this.height = height;
064    }
065    public MetsaMapFactory(int width, int height, long rngSeed)
066    {
067        rng = new StatefulRNG(new LightRNG(rngSeed));
068        this.width = width;
069        this.height = height;
070    }
071
072        public int getShadow(int x, int y, double[][] map) {
073        if (x >= width - 1 || y <= 0) {
074            return 0;
075        }
076        double upRight = map[x + 1][y - 1];
077        double right = map[x + 1][y];
078        double up = map[x][y - 1];
079        double cur = map[x][y];
080        if (cur <= 0) {
081            return 0;
082        }
083        double slope = cur - (upRight + up + right) / 3;
084        if (slope < SHADOWLIMIT && slope > -SHADOWLIMIT) {
085            return 0;
086        }
087        if (slope >= SHADOWLIMIT) {
088            return -1; //"alpha"
089        }
090        if (slope <= -SHADOWLIMIT) {
091            return 1;
092        } else {
093            return 0;
094        }
095    }
096
097    /**
098     * Finds and returns the closest point containing a city to the given point.
099     * Does not include provided point as a possible city location.
100     *
101     * If there are no cities, null is returned.
102     *
103     * @param point
104     * @return
105     */
106        public Coord closestCity(Coord point) {
107        double dist = 999999999, newdist;
108        Coord closest = null;
109        for (Coord c : cities) {
110            if (c.equals(point)) {
111                continue;//skip the one being tested for
112            }
113            newdist = Math.pow(point.x - c.x, 2) + Math.pow(point.y - c.y, 2);
114            if (newdist < dist) {
115                dist = newdist;
116                closest = c;
117            }
118        }
119        return closest;
120    }
121
122        public double[][] makeHeightMap() {
123        double[][] map = MapFactory.heightMap(width, height, rng);
124
125        for (int x = 0; x < width / 8; x++) {
126            for (int y = 0; y < height; y++) {
127                map[x][y] = map[x][y] - 1.0 + x / ((width - 1) * 0.125);
128                if (map[x][y] > maxPeak) {
129                    maxPeak = map[x][y];
130                }
131            }
132        }
133
134        for (int x = width / 8; x < 7 * width / 8; x++) {
135            for (int y = 0; y < height; y++) {
136                map[x][y] = map[x][y];
137                if (map[x][y] > maxPeak) {
138                    maxPeak = map[x][y];
139                }
140            }
141        }
142
143        for (int x = 7 * width / 8; x < width; x++) {
144            for (int y = 0; y < height; y++) {
145                map[x][y] = map[x][y] - 1.0 + (width - 1 - x) / ((width - 1) * 0.125);
146                if (map[x][y] > maxPeak) {
147                    maxPeak = map[x][y];
148                }
149            }
150        }
151
152        return map;
153    }
154
155        public int[][] makeBiomeMap(double[][] map) {
156        //biomes 0 normal 1 snow
157        int biomeMap[][] = new int[width][height];
158        for (int x = 0; x < width; x++) {
159            for (int y = 0; y < height; y++) {
160                biomeMap[x][y] = 0;
161                double distanceFromEquator = Math.abs(y - height / 2.0) / (height / 2.0);
162                distanceFromEquator += PerlinNoise.noise(x / 32, y / 32) / 8 + map[x][y] / 32;
163                if (distanceFromEquator > POLARLIMIT) {
164                    biomeMap[x][y] = 1;
165                }
166                if (distanceFromEquator < DESERTLIMIT) {
167                    biomeMap[x][y] = 2;
168                }
169                if (distanceFromEquator > POLARLIMIT + 0.25) {
170                    biomeMap[x][y] = 3;
171                }
172            }
173        }
174        return biomeMap;
175    }
176
177        public int[][] makeNationMap(double[][] map) {
178        // nationmap, 4 times less accurate map used for nations -1 no nation
179        int nationMap[][] = new int[width][height];
180        for (int i = 0; i < width / 4; i++) {
181            for (int j = 0; j < height / 4; j++) {
182                if (map[i * 4][j * 4] < 0) {
183                    nationMap[i][j] = -1;
184                } else {
185                    nationMap[i][j] = 0;
186                }
187            }
188        }
189        return nationMap;
190    }
191
192        public double[][] makeWeightedMap(double[][] map) {
193        //Weighted map for road
194        double weightedMap[][] = new double[width][height];
195        double SEALEVEL = 0;
196        double BEACHLEVEL = 0.05;
197        double PLAINSLEVEL = 0.3;
198        for (int i = 0; i < width / 4; i++) {
199            for (int j = 0; j < height / 4; j++) {
200                weightedMap[i][j] = 0;
201                if (map[i * 4][j * 4] > BEACHLEVEL) {
202                    weightedMap[i][j] = 2 + (map[i * 4][j * 4] - PLAINSLEVEL) * 8;
203                }
204                if (map[i][j] <= BEACHLEVEL && map[i * 4][j * 4] >= SEALEVEL) {
205                    weightedMap[i][j] = 2 - (map[i * 4][j * 4]) * 2;
206                }
207            }
208        }
209
210        for (int i = 0; i < CITYAMOUNT; i++) {
211            int px = rng.between(0, width);
212            int py = rng.between(0, height);
213            while (map[px][py] < SEALEVEL || map[px][py] > BEACHLEVEL) {
214                px = rng.between(0, width);
215                py = rng.between(0, height);
216            }
217            cities.add(Coord.get(4 * round(px / 4f), 4 * round(py / 4f)));
218        }
219        return weightedMap;
220    }
221
222    public List<Coord> getCities() {
223        return cities;
224    }
225
226    public double getMaxPeak() {
227        return maxPeak;
228    }
229}