001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.graphics.Color;
004import com.badlogic.gdx.math.MathUtils;
005import squidpony.squidgrid.mapping.WorldMapGenerator;
006import squidpony.squidmath.DiverRNG;
007import squidpony.squidmath.NumberTools;
008
009/**
010 * Created by Tommy Ettinger on 9/6/2019.
011 */
012public class WorldMapView {
013    protected int width, height;
014    protected float[][] colorMap;
015    protected WorldMapGenerator world;
016    protected WorldMapGenerator.DetailedBiomeMapper biomeMapper;
017
018    public int getWidth() {
019        return width;
020    }
021
022    public int getHeight() {
023        return height;
024    }
025
026    public float[][] getColorMap() {
027        return colorMap;
028    }
029
030    public WorldMapGenerator.DetailedBiomeMapper getBiomeMapper() {
031        return biomeMapper;
032    }
033
034    public void setBiomeMapper(WorldMapGenerator.DetailedBiomeMapper biomeMapper) {
035        this.biomeMapper = biomeMapper;
036    }
037
038    public WorldMapGenerator getWorld() {
039        return world;
040    }
041
042    public void setWorld(WorldMapGenerator world) {
043        this.world = world;
044        if(this.width != world.width || this.height != world.height)
045        {
046            width = world.width;
047            height = world.height;
048            colorMap = new float[width][height];
049        }
050    }
051
052    public WorldMapView(WorldMapGenerator worldMapGenerator)
053    {
054        world = worldMapGenerator == null ? new WorldMapGenerator.LocalMap() : worldMapGenerator;
055        width = world.width;
056        height = world.height;
057        colorMap = new float[width][height];
058        this.biomeMapper = new WorldMapGenerator.DetailedBiomeMapper();
059        initialize();
060    }
061    
062    public WorldMapView(long seed, int width, int height)
063    {
064        this(new WorldMapGenerator.LocalMap(seed, width, height));
065    }
066    
067    public static final int
068            Desert                 = 0 ,
069            Savanna                = 1 ,
070            TropicalRainforest     = 2 ,
071            Grassland              = 3 ,
072            Woodland               = 4 ,
073            SeasonalForest         = 5 ,
074            TemperateRainforest    = 6 ,
075            BorealForest           = 7 ,
076            Tundra                 = 8 ,
077            Ice                    = 9 ,
078            Beach                  = 10,
079            Rocky                  = 11,
080            Shallow                = 12,
081            Ocean                  = 13,
082            Empty                  = 14;
083
084    public static float iceColor = SColor.floatGetI(240, 248, 255);
085    public static float desertColor = SColor.floatGetI(248, 229, 180);
086    public static float savannaColor = SColor.floatGetI(181, 200, 100);
087    public static float tropicalRainforestColor = SColor.floatGetI(66, 123, 25);
088    public static float tundraColor = SColor.floatGetI(151, 175, 159);
089    public static float temperateRainforestColor = SColor.floatGetI(54, 113, 60);
090    public static float grasslandColor = SColor.floatGetI(169, 185, 105);
091    public static float seasonalForestColor = SColor.floatGetI(100, 158, 75);
092    public static float borealForestColor = SColor.floatGetI(75, 105, 45);
093    public static float woodlandColor = SColor.floatGetI(122, 170, 90);
094    public static float rockyColor = SColor.floatGetI(171, 175, 145);
095    public static float beachColor = SColor.floatGetI(255, 235, 180);
096    public static float emptyColor = SColor.floatGetI(34, 32, 52);
097
098    // water colors
099    public static float deepColor =    SColor.floatGetI(0, 42, 88);
100    public static float shallowColor = SColor.floatGetI(20, 145, 197);
101    public static float foamColor =    SColor.floatGetI(61,  162, 215);
102
103    protected float[] biomeColors = {
104            desertColor,
105            savannaColor,
106            tropicalRainforestColor,
107            grasslandColor,
108            woodlandColor,
109            seasonalForestColor,
110            temperateRainforestColor,
111            borealForestColor,
112            tundraColor,
113            iceColor,
114            beachColor,
115            rockyColor,
116            shallowColor,
117            deepColor,
118            emptyColor
119    };
120
121    public final static float[] BIOME_TABLE = {
122            //COLDEST   //COLDER      //COLD               //HOT                     //HOTTER                 //HOTTEST
123            Ice+0.85f,  Ice+0.65f,    Grassland+0.9f,      Desert+0.75f,             Desert+0.8f,             Desert+0.85f,            //DRYEST
124            Ice+0.7f,   Tundra+0.9f,  Grassland+0.6f,      Grassland+0.3f,           Desert+0.65f,            Desert+0.7f,             //DRYER
125            Ice+0.55f,  Tundra+0.7f,  Woodland+0.4f,       Woodland+0.6f,            Savanna+0.8f,            Desert+0.6f,             //DRY
126            Ice+0.4f,   Tundra+0.5f,  SeasonalForest+0.3f, SeasonalForest+0.5f,      Savanna+0.6f,            Savanna+0.4f,            //WET
127            Ice+0.2f,   Tundra+0.3f,  BorealForest+0.35f,  TemperateRainforest+0.4f, TropicalRainforest+0.6f, Savanna+0.2f,            //WETTER
128            Ice+0.0f,   BorealForest, BorealForest+0.15f,  TemperateRainforest+0.2f, TropicalRainforest+0.4f, TropicalRainforest+0.2f, //WETTEST
129            Rocky+0.9f, Rocky+0.6f,   Beach+0.4f,          Beach+0.55f,              Beach+0.75f,             Beach+0.9f,              //COASTS
130            Ice+0.3f,   Shallow+0.9f, Shallow+0.75f,       Shallow+0.6f,             Shallow+0.5f,            Shallow+0.4f,            //RIVERS
131            Ice+0.2f,   Shallow+0.9f, Shallow+0.65f,       Shallow+0.5f,             Shallow+0.4f,            Shallow+0.3f,            //LAKES
132            Ocean+0.9f, Ocean+0.75f,  Ocean+0.6f,          Ocean+0.45f,              Ocean+0.3f,              Ocean+0.15f,             //OCEANS
133            Empty                                                                                                                      //SPACE
134    };
135    public final float[] BIOME_COLOR_TABLE = new float[61], BIOME_DARK_COLOR_TABLE = new float[61];
136    
137    public void initialize()
138    {
139        initialize(0f, 0f, 0f, 1f);
140    }
141    
142    public void initialize(float hue, float saturation, float brightness, float contrast)
143    {
144        float b, diff;
145        for (int i = 0; i < 60; i++) {
146            b = BIOME_TABLE[i];
147            diff = ((b % 1.0f) - 0.48f) * 0.27f * contrast;
148            BIOME_COLOR_TABLE[i] = b = SColor.toEditedFloat((diff >= 0)
149                    ? SColor.lightenFloat(biomeColors[(int)b], diff)
150                    : SColor.darkenFloat(biomeColors[(int)b], -diff), hue, saturation, brightness, 0f);
151            BIOME_DARK_COLOR_TABLE[i] = SColor.darkenFloat(b, 0.08f);
152        }
153        BIOME_COLOR_TABLE[60] = BIOME_DARK_COLOR_TABLE[60] = emptyColor;
154    }
155
156    /**
157     * Initializes the colors to use for each biome (these are almost always mixed with other biome colors in practice).
158     * Each parameter may be null to use the default for an Earth-like world; otherwise it should be a libGDX
159     * {@link Color} or some subclass, like {@link SColor}. All non-null parameters should probably be fully opaque,
160     * except {@code emptyColor}, which is only used for world maps that show empty space (like a globe, as produced by
161     * {@link WorldMapGenerator.RotatingSpaceMap}).
162     * @param desertColor hot, dry, barren land; may be sandy, but many real-world deserts don't have much sand
163     * @param savannaColor hot, mostly-dry land with some parched vegetation; also called scrub or chaparral
164     * @param tropicalRainforestColor hot, extremely wet forests with dense rich vegetation
165     * @param grasslandColor prairies that are dry and usually wind-swept, but not especially hot or cold
166     * @param woodlandColor part-way between a prairie and a forest; not especially hot or cold
167     * @param seasonalForestColor forest that becomes barren in winter (deciduous trees); not especially hot or cold
168     * @param temperateRainforestColor forest that tends to be slightly warm but very wet
169     * @param borealForestColor forest that tends to be cold and very wet
170     * @param tundraColor very cold plains that still have some low-lying vegetation; also called taiga 
171     * @param iceColor cold barren land covered in permafrost; also used for rivers and lakes that are frozen
172     * @param beachColor sandy or otherwise light-colored shorelines; here, these are more common in warmer places
173     * @param rockyColor rocky or otherwise rugged shorelines; here, these are more common in colder places
174     * @param shallowColor the color of very shallow water; will be mixed with {@code deepColor} to get most ocean colors
175     * @param deepColor the color of very deep water; will be mixed with {@code shallowColor} to get most ocean colors
176     * @param emptyColor the color used for empty space off the edge of the world map; may be transparent
177     */
178    public void initialize(
179            Color desertColor,
180            Color savannaColor,
181            Color tropicalRainforestColor,
182            Color grasslandColor,
183            Color woodlandColor,
184            Color seasonalForestColor,
185            Color temperateRainforestColor,
186            Color borealForestColor,
187            Color tundraColor,
188            Color iceColor,
189            Color beachColor,
190            Color rockyColor,
191            Color shallowColor,
192            Color deepColor,
193            Color emptyColor
194    )
195    {
196        biomeColors[ 0] = desertColor == null ? WorldMapView.desertColor : desertColor.toFloatBits();
197        biomeColors[ 1] = savannaColor == null ? WorldMapView.savannaColor : savannaColor.toFloatBits();
198        biomeColors[ 2] = tropicalRainforestColor == null ? WorldMapView.tropicalRainforestColor : tropicalRainforestColor.toFloatBits();
199        biomeColors[ 3] = grasslandColor == null ? WorldMapView.grasslandColor : grasslandColor.toFloatBits();
200        biomeColors[ 4] = woodlandColor == null ? WorldMapView.woodlandColor : woodlandColor.toFloatBits();
201        biomeColors[ 5] = seasonalForestColor == null ? WorldMapView.seasonalForestColor : seasonalForestColor.toFloatBits();
202        biomeColors[ 6] = temperateRainforestColor == null ? WorldMapView.temperateRainforestColor : temperateRainforestColor.toFloatBits();
203        biomeColors[ 7] = borealForestColor == null ? WorldMapView.borealForestColor : borealForestColor.toFloatBits();
204        biomeColors[ 8] = tundraColor == null ? WorldMapView.tundraColor : tundraColor.toFloatBits();
205        biomeColors[ 9] = iceColor == null ? WorldMapView.iceColor : iceColor.toFloatBits();
206        biomeColors[10] = beachColor == null ? WorldMapView.beachColor : beachColor.toFloatBits();
207        biomeColors[11] = rockyColor == null ? WorldMapView.rockyColor : rockyColor.toFloatBits();
208        biomeColors[12] = shallowColor == null ? WorldMapView.shallowColor : shallowColor.toFloatBits();
209        biomeColors[13] = deepColor == null ? WorldMapView.deepColor : deepColor.toFloatBits();
210        biomeColors[14] = emptyColor == null ? WorldMapView.emptyColor : emptyColor.toFloatBits();
211        float b, diff;
212        for (int i = 0; i < 60; i++) {
213            b = BIOME_TABLE[i];
214            diff = ((b % 1.0f) - 0.48f) * 0.27f;
215            BIOME_COLOR_TABLE[i] = b = (diff >= 0)
216                    ? SColor.lightenFloat(biomeColors[(int)b], diff)
217                    : SColor.darkenFloat(biomeColors[(int)b], -diff);
218            BIOME_DARK_COLOR_TABLE[i] = SColor.darkenFloat(b, 0.08f);
219        }
220        BIOME_COLOR_TABLE[60] = BIOME_DARK_COLOR_TABLE[60] = biomeColors[14];
221        biomeColors[ 0] = WorldMapView.desertColor;
222        biomeColors[ 1] = WorldMapView.savannaColor;
223        biomeColors[ 2] = WorldMapView.tropicalRainforestColor;
224        biomeColors[ 3] = WorldMapView.grasslandColor;
225        biomeColors[ 4] = WorldMapView.woodlandColor;
226        biomeColors[ 5] = WorldMapView.seasonalForestColor;
227        biomeColors[ 6] = WorldMapView.temperateRainforestColor;
228        biomeColors[ 7] = WorldMapView.borealForestColor;
229        biomeColors[ 8] = WorldMapView.tundraColor;
230        biomeColors[ 9] = WorldMapView.iceColor;
231        biomeColors[10] = WorldMapView.beachColor;
232        biomeColors[11] = WorldMapView.rockyColor;
233        biomeColors[12] = WorldMapView.shallowColor;
234        biomeColors[13] = WorldMapView.deepColor;
235        biomeColors[14] = WorldMapView.emptyColor;
236    }
237
238    /**
239     * Initializes the colors to use in some combination for all biomes, without regard for what the biome really is.
240     * There should be at least one packed float color given in similarColors, but there can be many of them.
241     * @param similarColors an array or vararg of packed float colors with at least one element
242     */
243    public void match(
244            float... similarColors
245    )
246    {
247        for (int i = 0; i < 14; i++) {
248            biomeColors[i] = SColor.lerpFloatColors(similarColors[i % similarColors.length], similarColors[(i * 5 + 3) % similarColors.length], 0.5f);
249        }
250        biomeColors[14] = WorldMapView.emptyColor;
251        float b, diff;
252        for (int i = 0; i < 60; i++) {
253            b = BIOME_TABLE[i];
254            diff = ((b % 1.0f) - 0.48f) * 0.27f;
255            BIOME_COLOR_TABLE[i] = b = (diff >= 0)
256                    ? SColor.lightenFloat(biomeColors[(int)b], diff)
257                    : SColor.darkenFloat(biomeColors[(int)b], -diff);
258            BIOME_DARK_COLOR_TABLE[i] = SColor.darkenFloat(b, 0.08f);
259        }
260        BIOME_COLOR_TABLE[60] = BIOME_DARK_COLOR_TABLE[60] = biomeColors[14];
261        biomeColors[ 0] = WorldMapView.desertColor;
262        biomeColors[ 1] = WorldMapView.savannaColor;
263        biomeColors[ 2] = WorldMapView.tropicalRainforestColor;
264        biomeColors[ 3] = WorldMapView.grasslandColor;
265        biomeColors[ 4] = WorldMapView.woodlandColor;
266        biomeColors[ 5] = WorldMapView.seasonalForestColor;
267        biomeColors[ 6] = WorldMapView.temperateRainforestColor;
268        biomeColors[ 7] = WorldMapView.borealForestColor;
269        biomeColors[ 8] = WorldMapView.tundraColor;
270        biomeColors[ 9] = WorldMapView.iceColor;
271        biomeColors[10] = WorldMapView.beachColor;
272        biomeColors[11] = WorldMapView.rockyColor;
273        biomeColors[12] = WorldMapView.shallowColor;
274        biomeColors[13] = WorldMapView.deepColor;
275        biomeColors[14] = WorldMapView.emptyColor;
276    }
277
278    public void generate()
279    {
280//        generate(world.seedA, world.seedB, 0.9 + NumberTools.formCurvedDouble((world.seedA ^ 0x123456789ABCDL) * 0x12345689ABL ^ world.seedB) * 0.15,
281//                DiverRNG.determineDouble(world.seedB * 0x12345L + 0x54321L ^ world.seedA) * 0.2 + 1.0);
282        generate(world.seedA, world.seedB, 1.0 + NumberTools.formCurvedDouble((world.seedA ^ 0x123456789ABCDL) * 0x12345689ABL ^ world.seedB) * 0.25,
283                DiverRNG.determineDouble(world.seedB * 0x12345L + 0x54321L ^ world.seedA) * 0.25 + 1.0);
284    }
285    public void generate(double landMod, double heatMod)
286    {
287        generate(world.seedA, world.seedB, landMod, heatMod);
288    }
289    
290    public void generate(int seedA, int seedB, double landMod, double heatMod) {
291        long seed = (long) seedB << 32 | (seedA & 0xFFFFFFFFL);
292        world.generate(landMod, heatMod, seed);
293        biomeMapper.makeBiomes(world);
294    }
295    public float[][] show()
296    {
297        int hc, tc, bc;
298        final int[][] heightCodeData = world.heightCodeData;
299        double[][] heightData = world.heightData;
300        int[][] heatCodeData = biomeMapper.heatCodeData;
301        int[][] biomeCodeData = biomeMapper.biomeCodeData;
302
303        for (int y = 0; y < height; y++) {
304            PER_CELL:
305            for (int x = 0; x < width; x++) {
306                hc = heightCodeData[x][y];
307                if(hc == 1000)
308                {
309                    colorMap[x][y] = emptyColor;
310                    continue;
311                }
312                tc = heatCodeData[x][y];
313                bc = biomeCodeData[x][y];
314                if(tc == 0)
315                {
316                    switch (hc)
317                    {
318                        case 0:
319                        case 1:
320                        case 2:
321                        case 3:
322                            colorMap[x][y] = SColor.lerpFloatColors(BIOME_COLOR_TABLE[50], BIOME_COLOR_TABLE[12],
323                                    (float) ((heightData[x][y] - -1.0) / (WorldMapGenerator.sandLower - -1.0)));
324                            continue PER_CELL;
325                        case 4:
326                            colorMap[x][y] = SColor.lerpFloatColors(BIOME_COLOR_TABLE[0], BIOME_COLOR_TABLE[12],
327                                    (float) ((heightData[x][y] - WorldMapGenerator.sandLower) / (WorldMapGenerator.sandUpper - WorldMapGenerator.sandLower)));
328                            continue PER_CELL;
329                    }
330                }
331                switch (hc) {
332                    case 0:
333                    case 1:
334                    case 2:
335                    case 3:
336                        colorMap[x][y] = SColor.lerpFloatColors(
337                                BIOME_COLOR_TABLE[56], BIOME_COLOR_TABLE[43],
338                                (MathUtils.clamp((float) (((heightData[x][y] + 0.06) * 8.0) / (WorldMapGenerator.sandLower + 1.0)), 0f, 1f)));
339                        break;
340                    default:
341                        colorMap[x][y] = SColor.lerpFloatColors(BIOME_COLOR_TABLE[biomeMapper.extractPartB(bc)],
342                                BIOME_DARK_COLOR_TABLE[biomeMapper.extractPartA(bc)], biomeMapper.extractMixAmount(bc));
343                }
344            }
345        }
346        
347        return colorMap;
348    }
349}