/* * PerlinSolidNoiseGenerator.java 1.0 98/06/16 Carl Burke * * Encapsulates Perlin's method for solid noise generation. * * Copyright (c) 1998 Carl Burke. * * Adapted from copyrighted source code by Ken Perlin * and F. Kenton Musgrave to accompany: * Texturing and Modeling: A Procedural Approach * Ebert, D., Musgrave, K., Peachey, P., Perlin, K., and Worley, S. * AP Professional, September, 1994. ISBN 0-12-228760-6 * Web site: http://www.cs.umbc.edu/~ebert/book/book.html */ import java.util.*; public class PerlinSolidNoiseGenerator implements SolidNoiseGenerator { // *** METHODS OF TERRAIN GENERATION CURRENTLY SUPPORTED static public final int METHOD_BASIC = 1; static public final int METHOD_MULTIFRACTAL = 2; static public final int METHOD_HETERO_TERRAIN = 3; static public final int METHOD_HYBRID_MULTIFRACTAL = 4; static public final int METHOD_RIDGED_MULTIFRACTAL = 5; // *** PRIVATE DATA TO DRIVE TERRAIN CALCULATIONS private int method; private double H; private double lacunarity; private double octaves; private double offset; private double gain; private double[] point; private Random rgen; public PerlinSolidNoiseGenerator() { rgen = new Random(); init_noise(); point = new double[3]; method = METHOD_BASIC; H = 0.5; lacunarity = 2.0; octaves = 7.0; } public PerlinSolidNoiseGenerator(double hIn, double lacIn, double octIn) { rgen = new Random(); init_noise(); point = new double[3]; method = METHOD_BASIC; H = hIn; lacunarity = lacIn; octaves = octIn; } public PerlinSolidNoiseGenerator(int methIn, double hIn, double lacIn, double octIn, double offIn) { rgen = new Random(); init_noise(); point = new double[3]; switch (methIn) { case METHOD_MULTIFRACTAL: method = METHOD_MULTIFRACTAL; H = hIn; lacunarity = lacIn; octaves = octIn; offset = offIn; break; case METHOD_HETERO_TERRAIN: method = METHOD_HETERO_TERRAIN; H = hIn; lacunarity = lacIn; octaves = octIn; offset = offIn; break; case METHOD_HYBRID_MULTIFRACTAL: method = METHOD_HYBRID_MULTIFRACTAL; H = hIn; lacunarity = lacIn; octaves = octIn; offset = offIn; break; default: // don't know which method, so do basic method = METHOD_BASIC; H = hIn; lacunarity = lacIn; octaves = octIn; } } public PerlinSolidNoiseGenerator(double hIn, double lacIn, double octIn, double offIn, double gainIn) { rgen = new Random(); init_noise(); point = new double[3]; method = METHOD_RIDGED_MULTIFRACTAL; H = hIn; lacunarity = lacIn; octaves = octIn; offset = offIn; gain = gainIn; } ///** RANDOM NUMBER INITIALIZATION AND GENERATION *** private double rseed; public void setSeed(double s) { rseed = s; rgen.setSeed(Double.doubleToLongBits(rseed)); init_noise(); } public double getSeed() {return rseed;} /************************************************************ * Methods specific to noise synthetic terrain generation ************************************************************/ /************************************************************ * Supporting/filtering methods ************************************************************/ public double bias(double a, double b) { return Math.pow(a, Math.log(b) / Math.log(0.5)); } public double gain(double a, double b) { double p = Math.log(1. - b) / Math.log(0.5); if (a < .001) return 0.; else if (a > .999) return 1.; if (a < 0.5) return Math.pow(2 * a, p) / 2; else return 1. - Math.pow(2 * (1. - a), p) / 2; } private double vec[]; public double turbulence(double v[], double freq) { double t; if (vec==null) vec = new double[3]; for (t = 0. ; freq >= 1. ; freq /= 2) { vec[0] = freq * v[0]; vec[1] = freq * v[1]; vec[2] = freq * v[2]; t += Math.abs(noise3(vec)) / freq; } return t; } /************************************************************ * Initialization ************************************************************/ private void normalize2(double v[]) // v.length == 2 { double s; s = Math.sqrt(v[0] * v[0] + v[1] * v[1]); v[0] = v[0] / s; v[1] = v[1] / s; } private void normalize3(double v[]) // v.length == 3 { double s; s = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); v[0] = v[0] / s; v[1] = v[1] / s; v[2] = v[2] / s; } private void init_noise() { int i, j, k; p = new int[B + B + 2]; g3 = new double[B + B + 2][3]; g2 = new double[B + B + 2][2]; g1 = new double[B + B + 2]; for (i = 0 ; i < B ; i++) { p[i] = i; g1[i] = rgen.nextDouble() * 2.0 - 1.0; // -1.0 to 1.0 for (j = 0 ; j < 2 ; j++) g2[i][j] = rgen.nextDouble() * 2.0 - 1.0; // -1.0 to 1.0 normalize2(g2[i]); for (j = 0 ; j < 3 ; j++) g3[i][j] = rgen.nextDouble() * 2.0 - 1.0; // -1.0 to 1.0 normalize3(g3[i]); } while ((--i) > 0) { j = (int)(rgen.nextDouble() * B); k = p[i]; p[i] = p[j]; p[j] = k; } for (i = 0 ; i < B + 2 ; i++) { p[B + i] = p[i]; g1[B + i] = g1[i]; for (j = 0 ; j < 2 ; j++) g2[B + i][j] = g2[i][j]; for (j = 0 ; j < 3 ; j++) g3[B + i][j] = g3[i][j]; } } /************************************************************ * Noise generation (interpolation) over 1,2, and 3 dimensions ************************************************************/ /* noise functions over 1, 2, and 3 dimensions */ static final int B = 0x100; static final int BM = 0xff; static final int N = 0x1000; static final int NP = 12; /* 2^N */ static final int NM = 0xfff; private int p[]; private double g3[][]; private double g2[][]; private double g1[]; public double s_curve(double t) { return t * t * (3. - 2. * t); } public double lerp(double t, double a, double b) { return a + t * (b - a); } /* #define setup(i,b0,b1,r0,r1)\ t = vec[i] + N;\ b0 = ((int)t) & BM;\ b1 = (b0+1) & BM;\ r0 = t - (int)t;\ r1 = r0 - 1.;\ */ public double noise1(double arg) { int bx0, bx1; double rx0, rx1, sx, t, u, v; /* setup(0, bx0,bx1, rx0,rx1) */ t = arg + N; bx0 = ((int)t) & BM; bx1 = (bx0+1) & BM; rx0 = t - (int)t; rx1 = rx0 - 1.; /***/ sx = s_curve(rx0); u = rx0 * g1[ p[ bx0 ] ]; v = rx1 * g1[ p[ bx1 ] ]; return lerp(sx, u, v); } public double noise2(double vec[]) // vec.length == 2 { int bx0, bx1, by0, by1, b00, b10, b01, b11; double rx0, rx1, ry0, ry1, q[], sx, sy, a, b, t, u, v; int i, j; /* setup(0, bx0,bx1, rx0,rx1) */ t = vec[0] + N; bx0 = ((int)t) & BM; bx1 = (bx0+1) & BM; rx0 = t - (int)t; rx1 = rx0 - 1.; /***/ /* setup(1, by0,by1, ry0,ry1) */ t = vec[1] + N; by0 = ((int)t) & BM; by1 = (by0+1) & BM; ry0 = t - (int)t; ry1 = ry0 - 1.; /***/ i = p[ bx0 ]; j = p[ bx1 ]; b00 = p[ i + by0 ]; b10 = p[ j + by0 ]; b01 = p[ i + by1 ]; b11 = p[ j + by1 ]; sx = s_curve(rx0); sy = s_curve(ry0); q = g2[ b00 ] ; u = ( rx0 * q[0] + ry0 * q[1] ); q = g2[ b10 ] ; v = ( rx1 * q[0] + ry0 * q[1] ); a = lerp(sx, u, v); q = g2[ b01 ] ; u = ( rx0 * q[0] + ry1 * q[1] ); q = g2[ b11 ] ; v = ( rx1 * q[0] + ry1 * q[1] ); b = lerp(sx, u, v); return lerp(sy, a, b); } public double noise3(double vec[]) // vec.length == 3 { int bx0, bx1, by0, by1, bz0, bz1, b00, b10, b01, b11; double rx0, rx1, ry0, ry1, rz0, rz1, q[], sy, sz, a, b, c, d, t, u, v; int i, j; /* setup(0, bx0,bx1, rx0,rx1) */ t = vec[0] + N; bx0 = ((int)t) & BM; bx1 = (bx0+1) & BM; rx0 = t - (int)t; rx1 = rx0 - 1.; /***/ /* setup(1, by0,by1, ry0,ry1) */ t = vec[1] + N; by0 = ((int)t) & BM; by1 = (by0+1) & BM; ry0 = t - (int)t; ry1 = ry0 - 1.; /***/ /* setup(2, bz0,bz1, rz0,rz1) */ t = vec[2] + N; bz0 = ((int)t) & BM; bz1 = (bz0+1) & BM; rz0 = t - (int)t; rz1 = rz0 - 1.; /***/ i = p[ bx0 ]; j = p[ bx1 ]; b00 = p[ i + by0 ]; b10 = p[ j + by0 ]; b01 = p[ i + by1 ]; b11 = p[ j + by1 ]; t = s_curve(rx0); sy = s_curve(ry0); sz = s_curve(rz0); q = g3[ b00 + bz0 ] ; u = ( rx0 * q[0] + ry0 * q[1] + rz0 * q[2] ); q = g3[ b10 + bz0 ] ; v = ( rx1 * q[0] + ry0 * q[1] + rz0 * q[2] ); a = lerp(t, u, v); q = g3[ b01 + bz0 ] ; u = ( rx0 * q[0] + ry1 * q[1] + rz0 * q[2] ); q = g3[ b11 + bz0 ] ; v = ( rx1 * q[0] + ry1 * q[1] + rz0 * q[2] ); b = lerp(t, u, v); c = lerp(sy, a, b); q = g3[ b00 + bz1 ] ; u = ( rx0 * q[0] + ry0 * q[1] + rz1 * q[2] ); q = g3[ b10 + bz1 ] ; v = ( rx1 * q[0] + ry0 * q[1] + rz1 * q[2] ); a = lerp(t, u, v); q = g3[ b01 + bz1 ] ; u = ( rx0 * q[0] + ry1 * q[1] + rz1 * q[2] ); q = g3[ b11 + bz1 ] ; v = ( rx1 * q[0] + ry1 * q[1] + rz1 * q[2] ); b = lerp(t, u, v); d = lerp(sy, a, b); return lerp(sz, c, d); } public double noise(double vec[], int len) { switch (len) { case 0: return 0.; case 1: return noise1(vec[0]); case 2: return noise2(vec); default: return noise3(vec); } } /************************************************************ * Methods that use the noise functions to generate height fields * Adapted from code written by F. Kenton Musgrave ************************************************************/ /* * Procedural fBm evaluated at "point"; returns value stored in "value". * * Copyright 1994 F. Kenton Musgrave * * Parameters: * ``H'' is the fractal increment parameter * ``lacunarity'' is the gap between successive frequencies * ``octaves'' is the number of frequencies in the fBm * * 'point' must be a double[3] */ private boolean first_fBm = true; private double exponent_array[]; public double fBm( double point[], double H, double lacunarity, double octaves ) { double value, frequency, remainder; int i; /* precompute and store spectral weights */ if ( first_fBm ) { /* seize required memory for exponent_array */ exponent_array = new double[(int)octaves+1]; frequency = 1.0; for (i=0; i <= octaves; i++) { /* compute weight for each frequency */ exponent_array[i] = Math.pow( frequency, -H ); frequency *= lacunarity; } first_fBm = false; } value = 0.0; /* initialize vars to proper values */ frequency = 1.0; /* inner loop of spectral construction */ for (i=0; i < octaves; i++) { value += noise3( point ) * exponent_array[i]; point[0] *= lacunarity; point[1] *= lacunarity; point[2] *= lacunarity; } remainder = octaves - (int)octaves; if ( remainder != 0.0 ) { /* add in ``octaves'' remainder */ /* ``i'' and spatial freq. are preset in loop above */ value += remainder * noise3( point ) * exponent_array[i]; } return( value ); } /* * Procedural multifractal evaluated at "point"; * returns value stored in "value". * * Copyright 1994 F. Kenton Musgrave * * Parameters: * ``H'' determines the highest fractal dimension * ``lacunarity'' is gap between successive frequencies * ``octaves'' is the number of frequencies in the fBm * ``offset'' is the zero offset, which determines multifractality * * Note: this tends to yield very small values, so the results need * to be scaled appropriately. */ public double multifractal( double point[], double H, double lacunarity, double octaves, double offset ) { double value, frequency, remainder; int i; /* precompute and store spectral weights */ if ( first_fBm ) { /* seize required memory for exponent_array */ exponent_array = new double[(int)octaves+1]; frequency = 1.0; for (i=0; i <= octaves; i++) { /* compute weight for each frequency */ exponent_array[i] = Math.pow( frequency, -H ); frequency *= lacunarity; } first_fBm = false; } value = 1.0; /* initialize vars to proper values */ frequency = 1.0; /* inner loop of multifractal construction */ for (i=0; i < octaves; i++) { value *= offset * frequency * noise3( point ); point[0] *= lacunarity; point[1] *= lacunarity; point[2] *= lacunarity; } remainder = octaves - (int)octaves; if ( remainder != 0.0 ) { /* add in ``octaves'' remainder */ /* ``i'' and spatial freq. are preset in loop above */ value += remainder * noise3( point ) * exponent_array[i]; } return value; } /* * Heterogeneous procedural terrain function: stats by altitude method. * Evaluated at "point"; returns value stored in "value". * * Copyright 1994 F. Kenton Musgrave * * Parameters: * ``H'' determines the fractal dimension of the roughest areas * ``lacunarity'' is the gap between successive frequencies * ``octaves'' is the number of frequencies in the fBm * ``offset'' raises the terrain from `sea level' */ public double Hetero_Terrain( double point[], double H, double lacunarity, double octaves, double offset ) { double value, increment, frequency, remainder; int i; /* precompute and store spectral weights */ if ( first_fBm ) { /* seize required memory for exponent_array */ exponent_array = new double[(int)octaves+1]; frequency = 1.0; for (i=0; i <= octaves; i++) { /* compute weight for each frequency */ exponent_array[i] = Math.pow( frequency, -H ); frequency *= lacunarity; } first_fBm = false; } /* first unscaled octave of function; later octaves are scaled */ value = offset + noise3( point ); point[0] *= lacunarity; point[1] *= lacunarity; point[2] *= lacunarity; /* spectral construction inner loop, where the fractal is built */ for (i=1; i < octaves; i++) { /* obtain displaced noise value */ increment = noise3( point ) + offset; /* scale amplitude appropriately for this frequency */ increment *= exponent_array[i]; /* scale increment by current `altitude' of function */ increment *= value; /* add increment to ``value'' */ value += increment; /* raise spatial frequency */ point[0] *= lacunarity; point[1] *= lacunarity; point[2] *= lacunarity; } /* for */ /* take care of remainder in ``octaves'' */ remainder = octaves - (int)octaves; if ( remainder != 0.0) { /* ``i'' and spatial freq. are preset in loop above */ /* note that the main loop code is made shorter here */ /* you may want to that loop more like this */ increment = (noise3( point ) + offset) * exponent_array[i]; value += remainder * increment * value; } return( value ); } /* Hybrid additive/multiplicative multifractal terrain model. * * Copyright 1994 F. Kenton Musgrave * * Some good parameter values to start with: * * H: 0.25 * offset: 0.7 */ public double HybridMultifractal( double point[], double H, double lacunarity, double octaves, double offset ) { double frequency, result, signal, weight, remainder; int i; /* precompute and store spectral weights */ if ( first_fBm ) { /* seize required memory for exponent_array */ exponent_array = new double[(int)octaves+1]; frequency = 1.0; for (i=0; i <= octaves; i++) { /* compute weight for each frequency */ exponent_array[i] = Math.pow( frequency, -H ); frequency *= lacunarity; } first_fBm = false; } /* get first octave of function */ result = ( noise3( point ) + offset ) * exponent_array[0]; weight = result; /* increase frequency */ point[0] *= lacunarity; point[1] *= lacunarity; point[2] *= lacunarity; /* spectral construction inner loop, where the fractal is built */ for (i=1; i < octaves; i++) { /* prevent divergence */ if ( weight > 1.0 ) weight = 1.0; /* get next higher frequency */ signal = ( noise3( point ) + offset ) * exponent_array[i]; /* add it in, weighted by previous freq's local value */ result += weight * signal; /* update the (monotonically decreasing) weighting value */ /* (this is why H must specify a high fractal dimension) */ weight *= signal; /* increase frequency */ point[0] *= lacunarity; point[1] *= lacunarity; point[2] *= lacunarity; } /* for */ /* take care of remainder in ``octaves'' */ remainder = octaves - (int)octaves; if ( remainder != 0.0 ) { /* ``i'' and spatial freq. are preset in loop above */ result += remainder * noise3( point ) * exponent_array[i]; } return( result/2.0 - 1.0 ); } /* Ridged multifractal terrain model. * * Copyright 1994 F. Kenton Musgrave * * Some good parameter values to start with: * * H: 1.0 * offset: 1.0 * gain: 2.0 */ public double RidgedMultifractal( double point[], double H, double lacunarity, double octaves, double offset, double gain ) { double result, frequency, signal, weight; int i; /* precompute and store spectral weights */ if ( first_fBm ) { /* seize required memory for exponent_array */ exponent_array = new double[(int)octaves+1]; frequency = 1.0; for (i=0; i <= octaves; i++) { /* compute weight for each frequency */ exponent_array[i] = Math.pow( frequency, -H ); frequency *= lacunarity; } first_fBm = false; } /* get first octave */ signal = noise3( point ); /* get absolute value of signal (this creates the ridges) */ if ( signal < 0.0 ) signal = -signal; /* invert and translate (note that "offset" should be ~= 1.0) */ signal = offset - signal; /* square the signal, to increase "sharpness" of ridges */ signal *= signal; /* assign initial values */ result = signal; weight = 1.0; for( i=1; i < octaves; i++ ) { /* increase the frequency */ point[0] *= lacunarity; point[1] *= lacunarity; point[2] *= lacunarity; /* weight successive contributions by previous signal */ weight = signal * gain; if ( weight > 1.0 ) weight = 1.0; if ( weight < 0.0 ) weight = 0.0; signal = noise3( point ); if ( signal < 0.0 ) signal = -signal; signal = offset - signal; signal *= signal; /* weight the contribution */ signal *= weight; result += signal * exponent_array[i]; } return( (result-1.0)/2.0 ); } ///** COLOR INDEX CONSTANTS *** public static final int BLACK = 0; public static final int BLUE0 = 1; public static final int BLUE1 = 9; public static final int LAND0 = 10; public static final int LAND1 = 18; public static final int WHITE = 19; static int[] rtable = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 32, 48, 64, 80, 96,112,128, 255}; static int[] gtable = {0, 0, 16, 32, 48, 64, 80, 96,112,128, 255,240,224,208,192, 176,160,144,128, 255}; static int[] btable = {0, 255,255,255,255,255,255,255,255,255, 0, 4, 8, 12, 16, 20, 24, 28, 32, 255}; public boolean latic; // flag for latitude based colour public double land; // percentage of surface covered by land public double water;// percentage of surface covered by water ///*** METHODS REQUIRED BY INTERFACE /** * Sets internal variables required for a selected magnification, * image width, and image height. */ public void setScaling(double M, double W, double H) { } /** * Calculates an intensity value in [0.0,1.0] at the specified point. */ public double value(double x, double y, double z) { point[0] = x; point[1] = y; point[2] = z; switch (method) { case METHOD_BASIC: return fBm(point, H, lacunarity, octaves); case METHOD_MULTIFRACTAL: return multifractal(point, H, lacunarity, octaves, offset); case METHOD_HETERO_TERRAIN: return Hetero_Terrain(point, H, lacunarity, octaves, offset); case METHOD_HYBRID_MULTIFRACTAL: return HybridMultifractal(point, H, lacunarity, octaves, offset); case METHOD_RIDGED_MULTIFRACTAL: return RidgedMultifractal(point, H, lacunarity, octaves, offset, gain); } return 0.0; } /** * Returns an (alpha, red, green, blue) color value associated with * the value() at the specified point. */ public int color(double x, double y, double z) { double alt = value(x, y, z); int colour; // calculate colour if (alt <= 0.) // if below sea level then { water++; if (latic && y*y+alt >= 0.90) { // white if close to poles colour = WHITE; } else { // blue scale otherwise colour = BLUE1+(int)((BLUE1-BLUE0+1)*(2*alt)); if (colour < BLUE0) colour = BLUE0; } } else { land++; if (latic) alt += 0.10204*y*y; // altitude adjusted with latitude if (alt >= 0.5) // arbitrary, but not too bad { // if high then white colour = WHITE; } else { // else green to brown scale colour = LAND0+(int)((LAND1-LAND0+1)*(2*alt)); } } if (colour < 0) colour = 0; if (colour > 19) colour = 19; return(255 << 24 | rtable[colour] << 16 | gtable[colour] << 8 | btable[colour]); } /** * Returns an (alpha, red, green, blue) color value associated with * the background value in lieu of valid noise. */ public int background() { return 0xFF000000; } }