forked from TheAlgorithms/Java
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathPerlinNoise.java
More file actions
233 lines (201 loc) · 8.14 KB
/
PerlinNoise.java
File metadata and controls
233 lines (201 loc) · 8.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
package com.thealgorithms.others;
import java.util.Random;
import java.util.Scanner;
/**
* Utility for generating 2D value-noise blended across octaves (commonly known
* as Perlin-like noise).
*
* <p>
* The implementation follows the classic approach of:
* <ol>
* <li>Generate a base grid of random values in [0, 1).</li>
* <li>For each octave k, compute a layer by bilinear interpolation of the base
* grid
* at period 2^k.</li>
* <li>Blend all layers from coarse to fine using a geometric series of
* amplitudes
* controlled by {@code persistence}, then normalize to [0, 1].</li>
* </ol>
*
* <p>
* For background see:
* <a href="http://devmag.org.za/2009/04/25/perlin-noise/">Perlin Noise</a>.
*
* <p>
* Constraints and notes:
* <ul>
* <li>{@code width} and {@code height} should be positive.</li>
* <li>{@code octaveCount} must be at least 1 (0 would lead to a division by
* zero).</li>
* <li>{@code persistence} should be in (0, 1], typical values around
* 0.5–0.8.</li>
* <li>Given the same seed and parameters, results are deterministic.</li>
* </ul>
*/
public final class PerlinNoise {
private PerlinNoise() {
}
/**
* Generate a 2D array of blended noise values normalized to [0, 1].
*
* @param width width of the noise array (columns)
* @param height height of the noise array (rows)
* @param octaveCount number of octaves (layers) to blend; must be >= 1
* @param persistence per-octave amplitude multiplier in (0, 1]
* @param seed seed for the random base grid
* @return a {@code width x height} array containing blended noise values in [0,
* 1]
*/
static float[][] generatePerlinNoise(int width, int height, int octaveCount, float persistence, long seed) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
}
if (octaveCount < 1) {
throw new IllegalArgumentException("octaveCount must be >= 1");
}
if (!(persistence > 0f && persistence <= 1f)) { // using > to exclude 0 and NaN
throw new IllegalArgumentException("persistence must be in (0, 1]");
}
final float[][] base = createBaseGrid(width, height, seed);
final float[][][] layers = createLayers(base, width, height, octaveCount);
return blendAndNormalize(layers, width, height, persistence);
}
/** Create the base random lattice values in [0,1). */
static float[][] createBaseGrid(int width, int height, long seed) {
final float[][] base = new float[width][height];
Random random = new Random(seed);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
base[x][y] = random.nextFloat();
}
}
return base;
}
/** Pre-compute each octave layer at increasing frequency. */
static float[][][] createLayers(float[][] base, int width, int height, int octaveCount) {
final float[][][] noiseLayers = new float[octaveCount][][];
for (int octave = 0; octave < octaveCount; octave++) {
noiseLayers[octave] = generatePerlinNoiseLayer(base, width, height, octave);
}
return noiseLayers;
}
/** Blend layers using geometric amplitudes and normalize to [0,1]. */
static float[][] blendAndNormalize(float[][][] layers, int width, int height, float persistence) {
final int octaveCount = layers.length;
final float[][] out = new float[width][height];
float amplitude = 1f;
float totalAmplitude = 0f;
for (int octave = octaveCount - 1; octave >= 0; octave--) {
amplitude *= persistence;
totalAmplitude += amplitude;
final float[][] layer = layers[octave];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
out[x][y] += layer[x][y] * amplitude;
}
}
}
if (totalAmplitude <= 0f || Float.isInfinite(totalAmplitude) || Float.isNaN(totalAmplitude)) {
throw new IllegalStateException("Invalid totalAmplitude computed during normalization");
}
final float invTotal = 1f / totalAmplitude;
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
out[x][y] *= invTotal;
}
}
return out;
}
/**
* Generate a single octave layer by bilinear interpolation of a base grid at a
* given octave (period = 2^octave).
*
* @param base base random float array of size {@code width x height}
* @param width width of noise array
* @param height height of noise array
* @param octave current octave (0 for period 1, 1 for period 2, ...)
* @return float array containing the octave's interpolated values
*/
static float[][] generatePerlinNoiseLayer(float[][] base, int width, int height, int octave) {
float[][] perlinNoiseLayer = new float[width][height];
// Calculate period (wavelength) for different shapes.
int period = 1 << octave; // 2^k
float frequency = 1f / period; // 1/2^k
for (int x = 0; x < width; x++) {
// Calculate the horizontal sampling indices.
int x0 = (x / period) * period;
int x1 = (x0 + period) % width;
float horizontalBlend = (x - x0) * frequency;
for (int y = 0; y < height; y++) {
// Calculate the vertical sampling indices.
int y0 = (y / period) * period;
int y1 = (y0 + period) % height;
float verticalBlend = (y - y0) * frequency;
// Blend top corners.
float top = interpolate(base[x0][y0], base[x1][y0], horizontalBlend);
// Blend bottom corners.
float bottom = interpolate(base[x0][y1], base[x1][y1], horizontalBlend);
// Blend top and bottom interpolation to get the final value for this cell.
perlinNoiseLayer[x][y] = interpolate(top, bottom, verticalBlend);
}
}
return perlinNoiseLayer;
}
/**
* Linear interpolation between two values.
*
* @param a value at alpha = 0
* @param b value at alpha = 1
* @param alpha interpolation factor in [0, 1]
* @return interpolated value {@code (1 - alpha) * a + alpha * b}
*/
static float interpolate(float a, float b, float alpha) {
return a * (1 - alpha) + alpha * b;
}
/**
* Small demo that prints a text representation of the noise using a provided
* character set.
*/
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
final int width;
final int height;
final int octaveCount;
final float persistence;
final long seed;
final String charset;
final float[][] perlinNoise;
System.out.println("Width (int): ");
width = in.nextInt();
System.out.println("Height (int): ");
height = in.nextInt();
System.out.println("Octave count (int): ");
octaveCount = in.nextInt();
System.out.println("Persistence (float): ");
persistence = in.nextFloat();
System.out.println("Seed (long): ");
seed = in.nextLong();
System.out.println("Charset (String): ");
charset = in.next();
perlinNoise = generatePerlinNoise(width, height, octaveCount, persistence, seed);
final char[] chars = charset.toCharArray();
final int length = chars.length;
final float step = 1f / length;
// Output based on charset thresholds.
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
float value = step;
float noiseValue = perlinNoise[x][y];
for (char c : chars) {
if (noiseValue <= value) {
System.out.print(c);
break;
}
value += step;
}
}
System.out.println();
}
in.close();
}
}