44import java .util .Scanner ;
55
66/**
7- * For detailed info and implementation see: <a
8- * href="http://devmag.org.za/2009/04/25/perlin-noise/">Perlin-Noise</a>
7+ * Utility for generating 2D value-noise blended across octaves (commonly known
8+ * as Perlin-like noise).
9+ *
10+ * <p>The implementation follows the classic approach of:
11+ * <ol>
12+ * <li>Generate a base grid of random values in [0, 1).</li>
13+ * <li>For each octave k, compute a layer by bilinear interpolation of the base grid
14+ * at period 2^k.</li>
15+ * <li>Blend all layers from coarse to fine using a geometric series of amplitudes
16+ * controlled by {@code persistence}, then normalize to [0, 1].</li>
17+ * </ol>
18+ *
19+ * <p>For background see: <a href="http://devmag.org.za/2009/04/25/perlin-noise/">Perlin Noise</a>.
20+ *
21+ * <p>Constraints and notes:
22+ * <ul>
23+ * <li>{@code width} and {@code height} should be positive.</li>
24+ * <li>{@code octaveCount} must be at least 1 (0 would lead to a division by zero).</li>
25+ * <li>{@code persistence} should be in (0, 1], typical values around 0.5–0.8.</li>
26+ * <li>Given the same seed and parameters, results are deterministic.</li>
27+ * </ul>
928 */
1029public final class PerlinNoise {
11- private PerlinNoise () {
12- }
30+ private PerlinNoise () {}
1331
1432 /**
15- * @param width width of noise array
16- * @param height height of noise array
17- * @param octaveCount numbers of layers used for blending noise
18- * @param persistence value of impact each layer get while blending
19- * @param seed used for randomizer
20- * @return float array containing calculated "Perlin-Noise" values
33+ * Generate a 2D array of blended noise values normalized to [0, 1].
34+ *
35+ * @param width width of the noise array (columns)
36+ * @param height height of the noise array (rows)
37+ * @param octaveCount number of octaves (layers) to blend; must be >= 1
38+ * @param persistence per-octave amplitude multiplier in (0, 1]
39+ * @param seed seed for the random base grid
40+ * @return a {@code width x height} array containing blended noise values in [0, 1]
2141 */
2242 static float [][] generatePerlinNoise (int width , int height , int octaveCount , float persistence , long seed ) {
23- final float [][] base = new float [width ][height ];
24- final float [][] perlinNoise = new float [width ][height ];
25- final float [][][] noiseLayers = new float [octaveCount ][][];
43+ if (width <= 0 || height <= 0 ) {
44+ throw new IllegalArgumentException ("width and height must be > 0" );
45+ }
46+ if (octaveCount < 1 ) {
47+ throw new IllegalArgumentException ("octaveCount must be >= 1" );
48+ }
49+ if (!(persistence > 0f && persistence <= 1f )) { // using > to exclude 0 and NaN
50+ throw new IllegalArgumentException ("persistence must be in (0, 1]" );
51+ }
52+ final float [][] base = createBaseGrid (width , height , seed );
53+ final float [][][] layers = createLayers (base , width , height , octaveCount );
54+ return blendAndNormalize (layers , width , height , persistence );
55+ }
2656
57+ /** Create the base random lattice values in [0,1). */
58+ static float [][] createBaseGrid (int width , int height , long seed ) {
59+ final float [][] base = new float [width ][height ];
2760 Random random = new Random (seed );
28- // fill base array with random values as base for noise
2961 for (int x = 0 ; x < width ; x ++) {
3062 for (int y = 0 ; y < height ; y ++) {
3163 base [x ][y ] = random .nextFloat ();
3264 }
3365 }
66+ return base ;
67+ }
3468
35- // calculate octaves with different roughness
69+ /** Pre-compute each octave layer at increasing frequency. */
70+ static float [][][] createLayers (float [][] base , int width , int height , int octaveCount ) {
71+ final float [][][] noiseLayers = new float [octaveCount ][][];
3672 for (int octave = 0 ; octave < octaveCount ; octave ++) {
3773 noiseLayers [octave ] = generatePerlinNoiseLayer (base , width , height , octave );
3874 }
75+ return noiseLayers ;
76+ }
3977
78+ /** Blend layers using geometric amplitudes and normalize to [0,1]. */
79+ static float [][] blendAndNormalize (float [][][] layers , int width , int height , float persistence ) {
80+ final int octaveCount = layers .length ;
81+ final float [][] out = new float [width ][height ];
4082 float amplitude = 1f ;
4183 float totalAmplitude = 0f ;
4284
43- // calculate perlin noise by blending each layer together with specific persistence
4485 for (int octave = octaveCount - 1 ; octave >= 0 ; octave --) {
4586 amplitude *= persistence ;
4687 totalAmplitude += amplitude ;
47-
88+ final float [][] layer = layers [ octave ];
4889 for (int x = 0 ; x < width ; x ++) {
4990 for (int y = 0 ; y < height ; y ++) {
50- // adding each value of the noise layer to the noise
51- // by increasing amplitude the rougher noises will have more impact
52- perlinNoise [x ][y ] += noiseLayers [octave ][x ][y ] * amplitude ;
91+ out [x ][y ] += layer [x ][y ] * amplitude ;
5392 }
5493 }
5594 }
5695
57- // normalize values so that they stay between 0..1
96+ if (totalAmplitude <= 0f || Float .isInfinite (totalAmplitude ) || Float .isNaN (totalAmplitude )) {
97+ throw new IllegalStateException ("Invalid totalAmplitude computed during normalization" );
98+ }
99+
100+ final float invTotal = 1f / totalAmplitude ;
58101 for (int x = 0 ; x < width ; x ++) {
59102 for (int y = 0 ; y < height ; y ++) {
60- perlinNoise [x ][y ] /= totalAmplitude ;
103+ out [x ][y ] *= invTotal ;
61104 }
62105 }
63-
64- return perlinNoise ;
106+ return out ;
65107 }
66108
67109 /**
68- * @param base base random float array
110+ * Generate a single octave layer by bilinear interpolation of a base grid at a
111+ * given octave (period = 2^octave).
112+ *
113+ * @param base base random float array of size {@code width x height}
69114 * @param width width of noise array
70115 * @param height height of noise array
71- * @param octave current layer
72- * @return float array containing calculated "Perlin-Noise-Layer" values
116+ * @param octave current octave (0 for period 1, 1 for period 2, ...)
117+ * @return float array containing the octave's interpolated values
73118 */
74119 static float [][] generatePerlinNoiseLayer (float [][] base , int width , int height , int octave ) {
75120 float [][] perlinNoiseLayer = new float [width ][height ];
76121
77- // calculate period (wavelength) for different shapes
122+ // Calculate period (wavelength) for different shapes.
78123 int period = 1 << octave ; // 2^k
79124 float frequency = 1f / period ; // 1/2^k
80125
81126 for (int x = 0 ; x < width ; x ++) {
82- // calculates the horizontal sampling indices
127+ // Calculate the horizontal sampling indices.
83128 int x0 = (x / period ) * period ;
84129 int x1 = (x0 + period ) % width ;
85- float horizintalBlend = (x - x0 ) * frequency ;
130+ float horizontalBlend = (x - x0 ) * frequency ;
86131
87132 for (int y = 0 ; y < height ; y ++) {
88- // calculates the vertical sampling indices
133+ // Calculate the vertical sampling indices.
89134 int y0 = (y / period ) * period ;
90135 int y1 = (y0 + period ) % height ;
91136 float verticalBlend = (y - y0 ) * frequency ;
92137
93- // blend top corners
94- float top = interpolate (base [x0 ][y0 ], base [x1 ][y0 ], horizintalBlend );
138+ // Blend top corners.
139+ float top = interpolate (base [x0 ][y0 ], base [x1 ][y0 ], horizontalBlend );
95140
96- // blend bottom corners
97- float bottom = interpolate (base [x0 ][y1 ], base [x1 ][y1 ], horizintalBlend );
141+ // Blend bottom corners.
142+ float bottom = interpolate (base [x0 ][y1 ], base [x1 ][y1 ], horizontalBlend );
98143
99- // blend top and bottom interpolation to get the final blend value for this cell
144+ // Blend top and bottom interpolation to get the final value for this cell.
100145 perlinNoiseLayer [x ][y ] = interpolate (top , bottom , verticalBlend );
101146 }
102147 }
@@ -105,16 +150,21 @@ static float[][] generatePerlinNoiseLayer(float[][] base, int width, int height,
105150 }
106151
107152 /**
108- * @param a value of point a
109- * @param b value of point b
110- * @param alpha determine which value has more impact (closer to 0 -> a,
111- * closer to 1 -> b)
112- * @return interpolated value
153+ * Linear interpolation between two values.
154+ *
155+ * @param a value at alpha = 0
156+ * @param b value at alpha = 1
157+ * @param alpha interpolation factor in [0, 1]
158+ * @return interpolated value {@code (1 - alpha) * a + alpha * b}
113159 */
114160 static float interpolate (float a , float b , float alpha ) {
115161 return a * (1 - alpha ) + alpha * b ;
116162 }
117163
164+ /**
165+ * Small demo that prints a text representation of the noise using a provided
166+ * character set.
167+ */
118168 public static void main (String [] args ) {
119169 Scanner in = new Scanner (System .in );
120170
@@ -148,7 +198,7 @@ public static void main(String[] args) {
148198 final char [] chars = charset .toCharArray ();
149199 final int length = chars .length ;
150200 final float step = 1f / length ;
151- // output based on charset
201+ // Output based on charset thresholds.
152202 for (int x = 0 ; x < width ; x ++) {
153203 for (int y = 0 ; y < height ; y ++) {
154204 float value = step ;
0 commit comments