package squidpony.gdx.tests; import com.badlogic.gdx.*; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.utils.viewport.StretchViewport; import com.badlogic.gdx.utils.viewport.Viewport; import squidpony.squidgrid.gui.gdx.SColor; import squidpony.squidgrid.gui.gdx.SquidInput; import squidpony.squidgrid.gui.gdx.SquidMouse; import squidpony.squidgrid.gui.gdx.SquidPanel; import squidpony.squidgrid.mapping.WorldMapGenerator; import squidpony.squidmath.NumberTools; import squidpony.squidmath.SeededNoise; import squidpony.squidmath.StatefulRNG; /** * Port of Zachary Carter's world generation technique, https://github.com/zacharycarter/mapgen * It seems to mostly work now, though it only generates one view of the map that it renders (but biome, moisture, heat, * and height maps can all be requested from it). */ public class DetailedWorldMapDemo extends ApplicationAdapter { public static final int Desert = 0 , Savanna = 1 , TropicalRainforest = 2 , Grassland = 3 , Woodland = 4 , SeasonalForest = 5 , TemperateRainforest = 6 , BorealForest = 7 , Tundra = 8 , Ice = 9 , Beach = 10, Rocky = 11, River = 12; private static final int width = 314 * 4, height = 400; private SpriteBatch batch; private SquidPanel display;//, overlay; private static final int cellWidth = 1, cellHeight = 1; private SquidInput input; private Stage stage; private Viewport view; private StatefulRNG rng; private long seed; private WorldMapGenerator world; private final double[][] shadingData = new double[width][height]; private final int[][] heatCodeData = new int[width][height], moistureCodeData = new int[width][height], biomeUpperCodeData = new int[width][height], biomeLowerCodeData = new int[width][height]; private long ttg = 0; // time to generate public static final double coldestValueLower = 0.0, coldestValueUpper = 0.15, // 0 colderValueLower = 0.15, colderValueUpper = 0.31, // 1 coldValueLower = 0.31, coldValueUpper = 0.5, // 2 warmValueLower = 0.5, warmValueUpper = 0.69, // 3 warmerValueLower = 0.69, warmerValueUpper = 0.85, // 4 warmestValueLower = 0.85, warmestValueUpper = 1.0, // 5 driestValueLower = 0.0, driestValueUpper = 0.27, // 0 drierValueLower = 0.27, drierValueUpper = 0.4, // 1 dryValueLower = 0.4, dryValueUpper = 0.6, // 2 wetValueLower = 0.6, wetValueUpper = 0.8, // 3 wetterValueLower = 0.8, wetterValueUpper = 0.9, // 4 wettestValueLower = 0.9, wettestValueUpper = 1.0; // 5 private static float black = SColor.floatGetI(0, 0, 0), white = SColor.floatGet(0xffffffff); // Biome map colors private static float ice = SColor.ALICE_BLUE.toFloatBits(); private static float darkIce = SColor.lerpFloatColors(ice, black, 0.15f); private static float lightIce = white; private static float desert = SColor.floatGetI(248, 229, 180); private static float darkDesert = SColor.lerpFloatColors(desert, black, 0.15f); private static float savanna = SColor.floatGetI(181, 200, 100); private static float darkSavanna = SColor.lerpFloatColors(savanna, black, 0.15f); private static float tropicalRainforest = SColor.floatGetI(66, 123, 25); private static float darkTropicalRainforest = SColor.lerpFloatColors(tropicalRainforest, black, 0.15f); private static float tundra = SColor.floatGetI(151, 175, 159); private static float darkTundra = SColor.lerpFloatColors(tundra, black, 0.15f); private static float temperateRainforest = SColor.floatGetI(54, 113, 60); private static float darkTemperateRainforest = SColor.lerpFloatColors(temperateRainforest, black, 0.15f); private static float grassland = SColor.floatGetI(169, 185, 105); private static float darkGrassland = SColor.lerpFloatColors(grassland, black, 0.15f); private static float seasonalForest = SColor.floatGetI(100, 158, 75); private static float darkSeasonalForest = SColor.lerpFloatColors(seasonalForest, black, 0.15f); private static float borealForest = SColor.floatGetI(75, 105, 45); private static float darkBorealForest = SColor.lerpFloatColors(borealForest, black, 0.15f); private static float woodland = SColor.floatGetI(122, 170, 90); private static float darkWoodland = SColor.lerpFloatColors(woodland, black, 0.15f); private static float rocky = SColor.floatGetI(171, 175, 145); private static float darkRocky = SColor.lerpFloatColors(rocky, black, 0.15f); private static float beach = SColor.floatGetI(255, 235, 180); private static float darkBeach = SColor.lerpFloatColors(beach, black, 0.15f); // water colors private static float deepColor = SColor.floatGetI(0, 68, 128); private static float darkDeepColor = SColor.lerpFloatColors(deepColor, black, 0.15f); private static float mediumColor = SColor.floatGetI(0, 89, 159); private static float darkMediumColor = SColor.lerpFloatColors(mediumColor, black, 0.15f); private static float shallowColor = SColor.floatGetI(0, 123, 167); private static float darkShallowColor = SColor.lerpFloatColors(shallowColor, black, 0.15f); private static float coastalColor = SColor.lerpFloatColors(shallowColor, white, 0.3f); private static float darkCoastalColor = SColor.lerpFloatColors(coastalColor, black, 0.15f); private static float foamColor = SColor.floatGetI(61, 162, 215); private static float darkFoamColor = SColor.lerpFloatColors(foamColor, black, 0.15f); private static float iceWater = SColor.floatGetI(210, 255, 252); private static float coldWater = mediumColor; private static float riverWater = shallowColor; private static float riverColor = SColor.floatGetI(30, 120, 200); private static float sandColor = SColor.floatGetI(240, 240, 64); private static float grassColor = SColor.floatGetI(50, 220, 20); private static float forestColor = SColor.floatGetI(16, 160, 0); private static float rockColor = SColor.floatGetI(177, 167, 157); private static float snowColor = SColor.floatGetI(255, 255, 255); // Heat map colors private static float coldest = SColor.floatGetI(0, 255, 255); private static float colder = SColor.floatGetI(170, 255, 255); private static float cold = SColor.floatGetI(0, 229, 133); private static float warm = SColor.floatGetI(255, 255, 100); private static float warmer = SColor.floatGetI(255, 100, 0); private static float warmest = SColor.floatGetI(241, 12, 0); // Moisture map colors private static float dryest = SColor.floatGetI(255, 139, 17); private static float dryer = SColor.floatGetI(245, 245, 23); private static float dry = SColor.floatGetI(80, 255, 0); private static float wet = SColor.floatGetI(85, 255, 255); private static float wetter = SColor.floatGetI(20, 70, 255); private static float wettest = SColor.floatGetI(0, 0, 100); private static float[] biomeColors = { desert, savanna, tropicalRainforest, grassland, woodland, seasonalForest, temperateRainforest, borealForest, tundra, ice, beach, rocky, foamColor//SColor.floatGetI(255, 40, 80) }, biomeDarkColors = { darkDesert, darkSavanna, darkTropicalRainforest, darkGrassland, darkWoodland, darkSeasonalForest, darkTemperateRainforest, darkBorealForest, darkTundra, darkIce, darkBeach, darkRocky, darkFoamColor//SColor.floatGetI(225, 10, 20) }; protected final static float[] BIOME_TABLE = { //COLDEST //COLDER //COLD //HOT //HOTTER //HOTTEST Ice+0.7f, Ice+0.65f, Grassland+0.9f, Desert+0.75f, Desert+0.8f, Desert+0.85f, //DRYEST Ice+0.6f, Tundra+0.9f, Grassland+0.6f, Grassland+0.3f, Desert+0.65f, Desert+0.7f, //DRYER Ice+0.5f, Tundra+0.7f, Woodland+0.4f, Woodland+0.6f, Savanna+0.8f, Desert+0.6f, //DRY Ice+0.4f, Tundra+0.5f, SeasonalForest+0.3f, SeasonalForest+0.5f, Savanna+0.6f, Savanna+0.4f, //WET Ice+0.2f, Tundra+0.3f, BorealForest+0.35f, TemperateRainforest+0.4f, TropicalRainforest+0.6f, Savanna+0.2f, //WETTER Ice+0.0f, BorealForest, BorealForest+0.15f, TemperateRainforest+0.2f, TropicalRainforest+0.4f, TropicalRainforest+0.2f, //WETTEST Rocky+0.9f, Rocky+0.6f, Beach+0.4f, Beach+0.55f, Beach+0.75f, Beach+0.9f, //COASTS Ice+0.3f, River+0.8f, River+0.7f, River+0.6f, River+0.5f, River+0.4f, //RIVERS Ice+0.2f, River+0.7f, River+0.6f, River+0.5f, River+0.4f, River+0.3f, //LAKES }, BIOME_COLOR_TABLE = new float[54], BIOME_DARK_COLOR_TABLE = new float[54]; static { float b, diff; for (int i = 0; i < 54; i++) { b = BIOME_TABLE[i]; diff = ((b % 1.0f) - 0.48f) * 0.27f; BIOME_COLOR_TABLE[i] = (b = (diff >= 0) ? SColor.lerpFloatColors(biomeColors[(int)b], white, diff) : SColor.lerpFloatColors(biomeColors[(int)b], black, -diff)); BIOME_DARK_COLOR_TABLE[i] = SColor.lerpFloatColors(b, black, 0.08f); } } protected void makeBiomes() { final WorldMapGenerator world = this.world; final int[][] heightCodeData = world.heightCodeData; final double[][] heatData = world.heatData, moistureData = world.moistureData, heightData = world.heightData; int hc, mc, heightCode; double hot, moist, high, i_hot = 1.0 / this.world.maxHeat; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { heightCode = heightCodeData[x][y]; hot = heatData[x][y]; moist = moistureData[x][y]; high = heightData[x][y]; boolean isLake = heightCode >= 4 && world.partialLakeData.contains(x, y), isRiver = heightCode >= 4 && world.partialRiverData.contains(x, y); if (moist >= (wettestValueUpper - (wetterValueUpper - wetterValueLower) * 0.2)) { mc = 5; } else if (moist >= (wetterValueUpper - (wetValueUpper - wetValueLower) * 0.2)) { mc = 4; } else if (moist >= (wetValueUpper - (dryValueUpper - dryValueLower) * 0.2)) { mc = 3; } else if (moist >= (dryValueUpper - (drierValueUpper - drierValueLower) * 0.2)) { mc = 2; } else if (moist >= (drierValueUpper - (driestValueUpper) * 0.2)) { mc = 1; } else { mc = 0; } if (hot >= (warmestValueUpper - (warmerValueUpper - warmerValueLower) * 0.2) * i_hot) { hc = 5; } else if (hot >= (warmerValueUpper - (warmValueUpper - warmValueLower) * 0.2) * i_hot) { hc = 4; } else if (hot >= (warmValueUpper - (coldValueUpper - coldValueLower) * 0.2) * i_hot) { hc = 3; } else if (hot >= (coldValueUpper - (colderValueUpper - colderValueLower) * 0.2) * i_hot) { hc = 2; } else if (hot >= (colderValueUpper - (coldestValueUpper) * 0.2) * i_hot) { hc = 1; } else { hc = 0; } heatCodeData[x][y] = hc; moistureCodeData[x][y] = mc; biomeUpperCodeData[x][y] = isLake ? hc + 48 : (isRiver ? hc + 42 : ((heightCode == 4) ? hc + 36 : hc + mc * 6)); if (moist >= (wetterValueUpper + (wettestValueUpper - wettestValueLower) * 0.2)) { mc = 5; } else if (moist >= (wetValueUpper + (wetterValueUpper - wetterValueLower) * 0.2)) { mc = 4; } else if (moist >= (dryValueUpper + (wetValueUpper - wetValueLower) * 0.2)) { mc = 3; } else if (moist >= (drierValueUpper + (dryValueUpper - dryValueLower) * 0.2)) { mc = 2; } else if (moist >= (driestValueUpper + (drierValueUpper - drierValueLower) * 0.2)) { mc = 1; } else { mc = 0; } if (hot >= (warmerValueUpper + (warmestValueUpper - warmestValueLower) * 0.2) * i_hot) { hc = 5; } else if (hot >= (warmValueUpper + (warmerValueUpper - warmerValueLower) * 0.2) * i_hot) { hc = 4; } else if (hot >= (coldValueUpper + (warmValueUpper - warmValueLower) * 0.2) * i_hot) { hc = 3; } else if (hot >= (colderValueUpper + (coldValueUpper - coldValueLower) * 0.2) * i_hot) { hc = 2; } else if (hot >= (coldestValueUpper + (colderValueUpper - colderValueLower) * 0.2) * i_hot) { hc = 1; } else { hc = 0; } biomeLowerCodeData[x][y] = hc + mc * 6; if (isRiver || isLake) shadingData[x][y] = //((moist - minWet) / (maxWet - minWet)) * 0.45 + 0.15 - 0.14 * ((hot - minHeat) / (maxHeat - minHeat)) (moist * 0.35 + 0.6); else shadingData[x][y] = //(upperProximityH + upperProximityM - lowerProximityH - lowerProximityM) * 0.1 + 0.2 (heightCode == 4) ? (0.18 - high) / (0.08) : NumberTools.bounce((high + moist) * (4.1 + high - hot)) * 0.5 + 0.5; // * (7.5 + moist * 1.9 - hot * 0.9) } } } @Override public void create() { batch = new SpriteBatch(); display = new SquidPanel(width, height, cellWidth, cellHeight); view = new StretchViewport(width*cellWidth, height*cellHeight); stage = new Stage(view, batch); seed = 0xDEBACL; rng = new StatefulRNG(seed); world = new WorldMapGenerator.SphereMap(seed, width, height, SeededNoise.instance, 0.75); input = new SquidInput(new SquidInput.KeyHandler() { @Override public void handle(char key, boolean alt, boolean ctrl, boolean shift) { switch (key) { case SquidInput.ENTER: seed = rng.nextLong(); generate(seed); rng.setState(seed); break; case '=': case '+': zoomIn(); break; case '-': case '_': zoomOut(); break; case 'Q': case 'q': case SquidInput.ESCAPE: { Gdx.app.exit(); } } Gdx.graphics.requestRendering(); } }, new SquidMouse(1, 1, width, height, 0, 0, new InputAdapter() { @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { if(button == Input.Buttons.RIGHT) { zoomOut(screenX, screenY); Gdx.graphics.requestRendering(); } else { zoomIn(screenX, screenY); Gdx.graphics.requestRendering(); } return true; } })); generate(seed); rng.setState(seed); Gdx.input.setInputProcessor(input); display.setPosition(0, 0); stage.addActor(display); Gdx.graphics.setContinuousRendering(false); Gdx.graphics.requestRendering(); } public void zoomIn() { zoomIn(width >> 1, height >> 1); } public void zoomIn(int zoomX, int zoomY) { long startTime = System.currentTimeMillis(); world.zoomIn(1, zoomX, zoomY); makeBiomes(); ttg = System.currentTimeMillis() - startTime; } public void zoomOut() { zoomOut(width>>1, height>>1); } public void zoomOut(int zoomX, int zoomY) { long startTime = System.currentTimeMillis(); world.zoomOut(1, zoomX, zoomY); makeBiomes(); ttg = System.currentTimeMillis() - startTime; } public void generate(final long seed) { long startTime = System.currentTimeMillis(); world.generate(seed); makeBiomes(); ttg = System.currentTimeMillis() - startTime; } public void putMap() { // uncomment next line to generate maps as quickly as possible //generate(rng.nextLong()); display.erase(); int hc, tc; int[][] heightCodeData = world.heightCodeData; double[][] heightData = world.heightData; for (int y = 0; y < height; y++) { PER_CELL: for (int x = 0; x < width; x++) { hc = heightCodeData[x][y]; tc = heatCodeData[x][y]; if(tc == 0) { switch (hc) { case 0: case 1: case 2: case 3: display.put(x, y, SColor.lerpFloatColors(shallowColor, ice, (float) ((heightData[x][y] - -1.0) / (0.1 - -1.0)))); continue PER_CELL; case 4: display.put(x, y, SColor.lerpFloatColors(lightIce, ice, (float) ((heightData[x][y] - 0.1) / (0.18 - 0.1)))); continue PER_CELL; } } switch (hc) { case 0: case 1: case 2: case 3: display.put(x, y, SColor.lerpFloatColors(deepColor, coastalColor, (float) ((heightData[x][y] - -1.0) / (0.1 - -1.0)))); break; default: /* if(partialLakeData.contains(x, y)) System.out.println("LAKE x=" + x + ",y=" + y + ':' + (((heightData[x][y] - lowers[hc]) / (differences[hc])) * 19 + shadingData[x][y] * 13) * 0.03125f); else if(partialRiverData.contains(x, y)) System.out.println("RIVER x=" + x + ",y=" + y + ':' + (((heightData[x][y] - lowers[hc]) / (differences[hc])) * 19 + shadingData[x][y] * 13) * 0.03125f); */ display.put(x, y, SColor.lerpFloatColors(BIOME_COLOR_TABLE[biomeLowerCodeData[x][y]], BIOME_DARK_COLOR_TABLE[biomeUpperCodeData[x][y]], (float) //(((heightData[x][y] - lowers[hc]) / (differences[hc])) * 11 + shadingData[x][y]// * 21) * 0.03125f )); //display.put(x, y, SColor.lerpFloatColors(darkTropicalRainforest, desert, (float) (heightData[x][y]))); } } } } @Override public void render() { // standard clear the background routine for libGDX //Gdx.gl.glClearColor(0f, 0f, 0f, 1.0f); //Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); Gdx.gl.glDisable(GL20.GL_BLEND); // need to display the map every frame, since we clear the screen to avoid artifacts. putMap(); Gdx.graphics.setTitle("Map! Took " + ttg + " ms to generate"); // if we are waiting for the player's input and get input, process it. if (input.hasNext()) { input.next(); } // stage has its own batch and must be explicitly told to draw(). stage.draw(); } @Override public void resize(int width, int height) { super.resize(width, height); view.update(width, height, true); view.apply(true); } public static void main(String[] arg) { LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); config.title = "SquidLib Demo: Detailed World Map"; config.width = width * cellWidth; config.height = height * cellHeight; config.foregroundFPS = 60; //config.fullscreen = true; config.backgroundFPS = -1; config.addIcon("Tentacle-16.png", Files.FileType.Internal); config.addIcon("Tentacle-32.png", Files.FileType.Internal); config.addIcon("Tentacle-128.png", Files.FileType.Internal); new LwjglApplication(new DetailedWorldMapDemo(), config); } }