Java tutorial
/// Copyright Ian Parberry, September 2013. /// /// This file is made available under the GNU All-Permissive License. /// /// Copying and distribution of this file, with or without modification, /// are permitted in any medium without royalty provided the copyright /// notice and this notice are preserved. This file is offered as-is, /// without any warranty. /// /// Created by Ian Parberry, September 2013. /// Demo by Pablo Nuez. /// Last updated January 31, 2014. package com.redagent.world; import java.util.ArrayList; import java.util.Random; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Texture; import com.redagent.materials.Grass; import com.redagent.materials.Sand; import com.redagent.materials.Stone; import com.redagent.materials.Water; import com.redagent.nature.TallGrass; import com.redagent.nature.Tree; import com.redagent.physics.Direction; import com.redagent.worldgenerator.NatureGenerator; public class Amortized2DNoise { float[] uax, vax, ubx, vbx, uay, vay, uby, vby; // /< Amortized noise // tables. float[] spline; // /< Spline array. float[][] workspace; // /< Temporary workspace. int size; // /< Size of workspace. public static int CELLSIZE2D = Chunk.chunkSize; Pixmap pixmap; public ArrayList<Texture> textures = new ArrayList<Texture>(); public float lerp(float t, float a, float b) { return (a + t * (b - a)); } public Amortized2DNoise(int n) { uax = new float[n + 1]; // /< X coordinate of u used to compute a in the // Perlin noise algorithm. vax = new float[n + 1]; // /< X coordinate of v used to compute a in the // Perlin noise algorithm. ubx = new float[n + 1]; // /< X coordinate of u used to compute b in the // Perlin noise algorithm. vbx = new float[n + 1]; // /< X coordinate of v used to compute b in the // Perlin noise algorithm. uay = new float[n + 1]; // /< Y coordinate of u used to compute a in the // Perlin noise algorithm. uby = new float[n + 1]; // /< Y coordinate of v used to compute a in the // Perlin noise algorithm. vay = new float[n + 1]; // /< Y coordinate of u used to compute b in the // Perlin noise algorithm. vby = new float[n + 1]; // /< Y coordinate of v used to compute b in the // Perlin noise algorithm. spline = new float[n + 1]; workspace = new float[n + 1][n + 1]; } // / Fill amortized noise table bottom up. // / \param t Amortized noise table. // / \param s Initial value. // / \param n Granularity. void FillUp(float[] t, float s, int n) { float d = s / n; t[0] = 0.0f; for (int i = 1; i < n; i++) t[i] = t[i - 1] + d; } // FillUp // / Fill amortized noise table top down. // / \param t Amortized noise table. // / \param s Initial value. // / \param n Granularity. void FillDn(float[] t, float s, int n) { float d = -s / n; t[n - 1] = d; for (int i = n - 2; i >= 0; i--) t[i] = t[i + 1] + d; } public int h(int x) { long xl = (long) x; return (int) (1664525L * xl * xl + 1013904223L); // constants from the // book // "Numerical // Recipes" } // h1 // / A 2D hash function. // / This is the standard method for making a 2D hash function out of a 1D // hash function. // / \param x X coordinate of value to be hashed. // / \param y Y coordinate of value to be hashed. // / \return Hash of (x, y). public int h(int x, int y) { return h(h(x) + y); } // h // / Initialize the amortized noise tables // / as described in Ian Parberry's paper "Amortized Noise" (google it). // / \param x0 x coordinate of top left corner of cell. // / \param y0 y coordinate of top left corner of cell. // / \param n Granularity. void initEdgeTables(int x0, int y0, int n) { // compute gradients at corner points int b0 = h(x0, y0); int b1 = h(x0, y0 + 1); int b2 = h(x0 + 1, y0); int b3 = h(x0 + 1, y0 + 1); // fill inferred gradient tables from corner gradients FillUp(uax, (float) Math.cos((float) b0), n); FillDn(vax, (float) Math.cos((float) b1), n); FillUp(ubx, (float) Math.cos((float) b2), n); FillDn(vbx, (float) Math.cos((float) b3), n); FillUp(uay, (float) Math.sin((float) b0), n); FillUp(vay, (float) Math.sin((float) b1), n); FillDn(uby, (float) Math.sin((float) b2), n); FillDn(vby, (float) Math.sin((float) b3), n); } // initEdgeTables // / Initialize the spline table // / as described in Ian Parberry's paper "Amortized Noise" (google it). // / \param n Granularity. void initSplineTable(int n) { for (int i = 0; i < n; i++) { // for each table entry float t = (float) i / n; // offset between grid points spline[i] = t * t * t * (10.0f + 3.0f * t * (2.0f * t - 5.0f)); // quintic // spline } // for } // initSplineTable // / Compute a single point of a single octave of Perlin noise. This is // similar // / to Perlin's noise2 function except that it substitutes table lookups // for floating point // / multiplication as described in Ian Parberry's paper "Amortized Noise" // (google it). // / \param i x coordinate of point. // / \param j y coordinate of point. // / \return Noise value at (x, y). float getNoise(int i, int j) { float u = uax[j] + uay[i]; float v = vax[j] + vay[i]; float a = lerp(spline[j], u, v); u = ubx[j] + uby[i]; v = vbx[j] + vby[i]; float b = lerp(spline[j], u, v); return lerp(spline[i], a, b); } // getNoise // / Get a single octave of noise into a subcell. // / This differs from CAmortized2DNoise::addNoise in that it copies the // noise // / to the cell instead of adding it in. // / \param n Granularity. // / \param i0 x offset of this subcell in cell. // / \param j0 y offset of this subcell in cell. // / \param cell Noise cell. void getNoise(int n, int i0, int j0, float[][] cell) { for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) cell[i0 + i][j0 + j] = getNoise(i, j); // this is the only line // that differs from // addNoise } // getNoise // / Add a single octave of noise into a subcell. // / This differs from CAmortized2DNoise::getNoise in that it adds the noise // / to the cell instead of copying it there. // / \param n Granularity. // / \param i0 x offset of this subcell in cell. // / \param j0 y offset of this subcell in cell. // / \param scale Noise is to be rescaled by this factor. // / \param cell Noise cell. void addNoise(int n, int i0, int j0, float scale, float[][] cell) { for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) cell[i0 + i][j0 + j] += scale * getNoise(i, j); // this is the // only line // that differs // from getNoise } // addNoise // / Generate a cell of 1/f amortized noise with persistence 0.5 and // lacunarity 2.0. // / See Ian Parberry's paper "Amortized Noise" (google it). // / \param x x coordinate of top left corner of cell. // / \param y y coordinate of top left corner of cell. // / \param m0 First octave. // / \param m1 Last octave. // / \param n Granularity. // / \param cell Cell to put generated noise into. // / \return Multiply noise by this to get into the range -1..1 float generate(int x, int y, int m0, int m1, int n, float[][] cell) { int r = 1; // Side length of cell divided by side length of subcell. // Skip over unwanted octaves. for (int i = 1; i < m0; i++) { n /= 2; r += r; } // for if (n < 2) return 1.0f; // fail and bail - should not happen // Generate first octave directly into cell. // We could add all octaves into the cell directly if we zero out the // cell before we begin. // However, that is a nontrivial overhead that Perlin noise does not // have, and we can avoid // it too by putting in the first octave and adding in the rest. initSplineTable(n); // initialize the spline table to cells of size n for (int i0 = 0; i0 < r; i0++) for (int j0 = 0; j0 < r; j0++) { // for each subcell initEdgeTables(x + i0, y + j0, n); // initialize the amortized // noise tables getNoise(n, i0 * n, j0 * n, cell); // generate noise directly // into cell } // for float scale = 1.0f; // scale factor // Generate the other octaves and add them into cell. See previous // comment. for (int k = m0; k < m1 && n >= 2; k++) { // for each octave after the // first n /= 2; r += r; x += x; y += y; scale *= 0.5f; // rescale for next octave initSplineTable(n); // initialize the spline table to cells of size // n for (int i0 = 0; i0 < r; i0++) for (int j0 = 0; j0 < r; j0++) { // for each subcell initEdgeTables(x + i0, y + j0, n); // initialize the // amortized noise // tables addNoise(n, i0 * n, j0 * n, scale, cell); // generate noise // directly into // cell } // for } // for each octave // Compute 1/magnitude and return it. // A single octave of Perlin noise returns a value of magnitude at most // 1/sqrt(2). // Adding magnitudes over all scaled octaves gives a total magnitude of // (1 + 0.5 + 0.25 +...+ scale)/sqrt(2). This is equal to (2 - // scale)/sqrt(2) // (using the standard formula for the sum of a geometric progression). // 1/magnitude is therefore sqrt(2)/(2-scale). return 1.4142f / (2.0f - scale); // multiply this by cell values to // bring them to [-1,1] } // generate public static Color colorConv(int r, int g, int b, float a) { return new Color((float) r / 255, (float) g / 255, (float) b / 255, a); } public MapTile[][] Generate2DNoise(Chunk c, MapTile[][] tiles, float[][] cell, int octave0, int octave1, int nRow, int nCol) { for (int i = 1; i < octave0; i++) { nCol = nCol * 2; nRow = nRow * 2; } generate(nCol, nRow, octave0, octave1, CELLSIZE2D, cell); float seaLevel = NatureGenerator.seaLevel; float sandAmount = NatureGenerator.sandAmount; for (int cy = 0; cy < CELLSIZE2D; cy++) { for (int cx = 0; cx < CELLSIZE2D; cx++) { if (cell[cx][cy] >= seaLevel && cell[cx][cy] <= seaLevel + sandAmount) { tiles[cx][cy] = new MapTile(c, cx, cy, false, Direction.NORTH, new Sand()); } if (cell[cx][cy] > seaLevel + 0.1f && cell[cx][cy] <= seaLevel + 0.4f) { tiles[cx][cy] = new MapTile(c, cx, cy, false, Direction.NORTH, new Grass()); } if (cell[cx][cy] > seaLevel + 0.4f) { tiles[cx][cy] = new MapTile(c, cx, cy, false, Direction.NORTH, new Stone()); } if (cell[cx][cy] < seaLevel) { tiles[cx][cy] = new MapTile(c, cx, cy, false, Direction.NORTH, new Water()); } } } for (int i = 0; i < CELLSIZE2D * CELLSIZE2D / 400; i++) { randomTree(cell, tiles); randomGrass(cell, tiles); } return tiles; } private void randomGrass(float[][] cell, MapTile[][] tiles) { Random rand = NatureGenerator.random; int x = rand.nextInt(CELLSIZE2D); int y = rand.nextInt(CELLSIZE2D); for (int j = 0; j < 10; j++) { int xx = x + rand.nextInt(15) - rand.nextInt(15); int yy = y + rand.nextInt(15) - rand.nextInt(15); if (xx >= 0 && yy >= 0 && xx < CELLSIZE2D && yy < CELLSIZE2D) { if (tiles[xx][yy].material.isSame(Grass.class)) { if (tiles[xx][yy].nature == null) { tiles[xx][yy].nature = new TallGrass(); } } } } } private void randomTree(float[][] cell, MapTile[][] tiles) { Random rand = NatureGenerator.random; int x = rand.nextInt(CELLSIZE2D); int y = rand.nextInt(CELLSIZE2D); for (int j = 0; j < 10; j++) { int xx = x + rand.nextInt(15) - rand.nextInt(15); int yy = y + rand.nextInt(15) - rand.nextInt(15); if (xx >= 0 && yy >= 0 && xx < CELLSIZE2D && yy < CELLSIZE2D) { if (tiles[xx][yy].material.isSame(Grass.class)) { tiles[xx][yy].nature = new Tree(); tiles[xx][yy].setSolid(true); } } } } public void dispose() { uax = null; vax = null; ubx = null; vbx = null; uay = null; vay = null; uby = null; vby = null; // /< Amortized noise spline = null; workspace = null; } }