/* * Copyright (c) 2012 Felix Mo. All rights reserved. * * CitySim is published under the terms of the MIT License. See the LICENSE file for more information. * */ import java.util.Random; import java.lang.Math; import java.text.DecimalFormat; /* * The terrain generator uses a random displacement fractal to generate noise that resemebles landforms * Decimal values are returned and parsed into tiles * * Created with the help of Stack Overflow question: * http://stackoverflow.com/questions/5531019/perlin-noise-in-java/5532726 * * Question by James Thornton (http://stackoverflow.com/users/690007/jt78) * http://stackoverflow.com/questions/5531019/perlin-noise-in-java/5532726 * * Answer by Simon G. (http://stackoverflow.com/users/682965/simon-g) & edited by Andrew Thompson (http://stackoverflow.com/users/418556/andrew-thompson) * http://stackoverflow.com/questions/5531019/perlin-noise-in-java/5532726#5532726 */ public class TerrainGenerator { /** Source of entropy */ private Random rand_; /** Amount of roughness */ float roughness_; /** Plasma fractal grid */ private float[][] grid; /** Tile grid */ private int[][] tiles; /** Generate a noise source based upon the midpoint displacement fractal. * * @param rand The random number generator * @param roughness a roughness parameter * @param width the width of the grid * @param height the height of the grid */ public TerrainGenerator(Random rand, float roughness, int width, int height) { roughness_ = roughness / width; grid = new float[width][height]; tiles = new int[width][height]; rand_ = (rand == null) ? new Random() : rand; initialise(); convertToTiles(); remove(); remove(); remove(); } public void initialise() { int xh = grid.length - 1; int yh = grid[0].length - 1; // set the corner points grid[0][0] = rand_.nextFloat() - 0.5f; grid[0][yh] = rand_.nextFloat() - 0.5f; grid[xh][0] = rand_.nextFloat() - 0.5f; grid[xh][yh] = rand_.nextFloat() - 0.5f; // generate the fractal generate(0, 0, xh, yh); } // Add a suitable amount of random displacement to a point private float roughen(float v, int l, int h) { return v + roughness_ * (float) (rand_.nextGaussian() * (h - l)); } // generate the fractal private void generate(int xl, int yl, int xh, int yh) { int xm = (xl + xh) / 2; int ym = (yl + yh) / 2; if ((xl == xm) && (yl == ym)) return; grid[xm][yl] = 0.5f * (grid[xl][yl] + grid[xh][yl]); grid[xm][yh] = 0.5f * (grid[xl][yh] + grid[xh][yh]); grid[xl][ym] = 0.5f * (grid[xl][yl] + grid[xl][yh]); grid[xh][ym] = 0.5f * (grid[xh][yl] + grid[xh][yh]); float v = roughen(0.5f * (grid[xm][yl] + grid[xm][yh]), xl + yl, yh + xh); grid[xm][ym] = v; grid[xm][yl] = roughen(grid[xm][yl], xl, xh); grid[xm][yh] = roughen(grid[xm][yh], xl, xh); grid[xl][ym] = roughen(grid[xl][ym], yl, yh); grid[xh][ym] = roughen(grid[xh][ym], yl, yh); generate(xl, yl, xm, ym); generate(xm, yl, xh, ym); generate(xl, ym, xm, yh); generate(xm, ym, xh, yh); } /** * Convert the plasma fractal grid to CitySim tiles */ private void convertToTiles() { DecimalFormat df = new DecimalFormat("#.#"); for (int x = 0; x < grid.length; x++) { for (int y = 0; y < grid[x].length; y++) { float value = 0.0f; try { value = df.parse(df.format(grid[x][y])).floatValue(); } catch (Exception e) { e.printStackTrace(); } if (x < 3 || y < 3 || x > 200-4 || y > 200-4) { // Set the outter tiles to be empty tiles[x][y] = Tile.EMPTY; } else { if (value <= 0.0f) { // WATER tiles[x][y] = Tile.WATER; } else { // LAND tiles[x][y] = Tile.GROUND; } } } } } private void remove() { for (int x = 0; x < tiles.length; x++) { for (int y = 0; y < tiles[x].length; y++) { if (tiles[x][y] == Tile.WATER) { // Check if tile is water int sides = 0; // # of sides touching land // check UP for land if (y+1 <= tiles[x].length-1) { if (tiles[x][y+1] == Tile.GROUND) { sides++; } } // check DOWN for land if (y-1 >= 0) { if (tiles[x][y-1] == Tile.GROUND) { sides++; } } // check LEFT for land if (x-1 >= 0) { if (tiles[x-1][y] == Tile.GROUND) { sides++; } } // check RIGHT for land if (x+1 <= tiles.length-1) { if (tiles[x+1][y] == Tile.GROUND) { sides++; } } // Change tile to ground if the body of water is 1 tile wide if (sides >= 3) { tiles[x][y] = Tile.GROUND; } } else if (tiles[x][y] == Tile.GROUND) { int sides = 0; // # of sides touching water // check UP for water if (y+1 <= tiles[x].length-1) { if (tiles[x][y+1] == Tile.WATER) { sides++; } } // check DOWN for water if (y-1 >= 0) { if (tiles[x][y-1] == Tile.WATER) { sides++; } } // check LEFT for water if (x-1 >= 0) { if (tiles[x-1][y] == Tile.WATER) { sides++; } } // check RIGHT for water if (x+1 <= tiles.length-1) { if (tiles[x+1][y] == Tile.WATER) { sides++; } } // Change tile to ground if the landmass is 1 tile wide if (sides >= 3) { tiles[x][y] = Tile.WATER; } } } } } // NOT IN USE /* private void smooth() { for (int x = 0; x < tiles.length; x++) { for (int y = 0; y < tiles[x].length; y++) { if (tiles[x][y] == Tile.GROUND) { boolean up = false, down = false, left = false, right = false; // side touching water // int sides = 0; // # of sides touching water // check UP for water if (y+1 <= tiles[x].length-1) { if (tiles[x][y+1] == Tile.WATER) { up = true; // sides++; } } // check DOWN for water if (y-1 >= 0) { if (tiles[x][y-1] == Tile.WATER) { down = true; // sides++; } } // check LEFT for water if (x-1 >= 0) { if (tiles[x-1][y] == Tile.WATER) { left = true; // sides++; } } if (up && left) { // top left corner // beach_bottom_right // tiles[x][y] = Tile.BEACH_TOP_LEFT; } else if (up && right) { // top right corner // beach_bottom_left // tiles[x][y] = Tile.BEACH_TOP_LEFT; } else if (up) { // top // bottom tiles[x][y] = Tile.BEACH_TOP; } else if (down && left) { // bottom left corner // top_right // tiles[x][y] = Tile.BEACH_BOTTOM_LEFT; } else if (down && right) { // bottom right corner // top_left // tiles[x][y] = Tile.BEACH_BOTTOM_RIGHT; } else if (down) { // bottom // top tiles[x][y] = Tile.BEACH_BOTTOM; } else if (left) { // left // right tiles[x][y] = Tile.BEACH_RIGHT; } else if (right) { // right // left tiles[x][y] = Tile.BEACH_LEFT; } } } } } */ /** * Dump out as a CSV */ public void printAsCSV() { for(int i = 0;i < grid.length;i++) { for(int j = 0;j < grid[0].length;j++) { System.out.print(grid[i][j]); System.out.print(","); } System.out.println(); } } /** * Convert to a Boolean array * @return the boolean array */ public boolean[][] toBooleans() { int w = grid.length; int h = grid[0].length; boolean[][] ret = new boolean[w][h]; for(int i = 0;i < w;i++) { for(int j = 0;j < h;j++) { ret[i][j] = grid[i][j] < 0; } } return ret; } /* * ACCESSORS */ public int[][] tiles() { return this.tiles; } }