From 0098621ebe89f57f3a0290c1cb19c476fd9abd02 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Thu, 14 Jul 2022 17:59:17 +0200 Subject: [PATCH] =?UTF-8?q?Klasse=20f=C3=BCr=20Perlin=20Noise=20implementi?= =?UTF-8?q?ert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Klasse erzeugt Zuffalswerte nach dem von Ken Perlin erfundenen „Improved Perlin Noise“ Algorithmus. --- src/main/java/schule/ngb/zm/Constants.java | 2 +- src/main/java/schule/ngb/zm/util/Noise.java | 548 ++++++++++-------- .../java/schule/ngb/zm/util/NoiseTest.java | 4 + 3 files changed, 308 insertions(+), 246 deletions(-) create mode 100644 src/test/java/schule/ngb/zm/util/NoiseTest.java diff --git a/src/main/java/schule/ngb/zm/Constants.java b/src/main/java/schule/ngb/zm/Constants.java index f3055cc..b9436f0 100644 --- a/src/main/java/schule/ngb/zm/Constants.java +++ b/src/main/java/schule/ngb/zm/Constants.java @@ -1327,7 +1327,7 @@ public class Constants { * @return Ein zufälliger Wert. */ public static final double noise() { - return getNoise().noise(N++); + return getNoise().noise(0.005 * N++); } /** diff --git a/src/main/java/schule/ngb/zm/util/Noise.java b/src/main/java/schule/ngb/zm/util/Noise.java index a20168f..e638f49 100644 --- a/src/main/java/schule/ngb/zm/util/Noise.java +++ b/src/main/java/schule/ngb/zm/util/Noise.java @@ -5,304 +5,362 @@ import java.util.Random; /** * Zufallsgenerator für Perlin-Noise. *

- * Die Implementierung basiert auf dem von Ken perlin entwickelten Algorithmus - * und wurde von Matthew A. Johnston für Java implementiert. - *

- * Original KOmmentar: - *

- * This is Ken Perlin's implementation of Perlin Noise but modified to be more
- * OOP.
- * 
- * - * @author Ken Perlin, Matthew A. Johnston (WarmWaffles) + * Die Implementierung basiert auf dem von Ken Perlin entwickelten Algorithmus + * und wurde anhand der Beschreibung von + * FLAFLA2 implementiert. */ public class Noise { - private Random rand; + private static final int N = 256; - private static final int P = 8; - - private static final int B = 1 << P; - - private static final int M = B - 1; - - private static final int NP = 8; - - private static final int N = 1 << NP; + private static final int M = N - 1; + /** + * Interne Permutationstabelle für diesen Generator + */ private int[] p; - private double[][] g2; + private double octaves = 1, persistence = .5; - private double[] g1; + private double frequency = 1; - private static double[][] points; + private double amplitude = 1; + private int repeat = -1; + + private double rangeMin = 0.0, rangeMax = 1.0; public Noise() { - this(System.nanoTime()); + this(null); } public Noise( long seed ) { - this(new Random(seed)); - } - - public Noise( Random rand ) { - p = new int[B + B + 2]; - g2 = new double[B + B + 2][2]; - g1 = new double[B + B + 2]; - - points = new double[32][3]; - - this.rand = rand; - init(); + init(new Random(seed)); } /** - * A stub function that calls {@link #init()} + * Initialisiert diesen Perlin-Noise mit dem angegebenen Zufallsgenerator. * - * @param seed + * @param rand */ - public void setSeed( int seed ) { - this.rand = new Random(seed); - init(); + public Noise( Random rand ) { + init(rand); } - public void setRandom( Random rand ) { - this.rand = rand; - init(); + public double getOctaves() { + return octaves; } - public double noise( double x, double y, double z ) { - int bx, by, bz, b0, b1, b00, b10, b01, b11; - double rx0, rx1, ry0, ry1, rz, sx, sy, sz, a, b, c, d, u, v, q[]; - - bx = (int) Math.IEEEremainder(Math.floor(x), B); - if( bx < 0 ) - bx += B; - - rx0 = x - Math.floor(x); - rx1 = rx0 - 1; - - by = (int) Math.IEEEremainder(Math.floor(y), B); - if( by < 0 ) - by += B; - - ry0 = y - Math.floor(y); - ry1 = ry0 - 1; - - bz = (int) Math.IEEEremainder(Math.floor(z), B); - if( bz < 0 ) - bz += B; - - rz = z - Math.floor(z); - - /* - if (bx < 0 || bx >= B + B + 2) - System.out.println(bx); - */ - b0 = p[bx]; - - bx++; - - b1 = p[bx]; - - b00 = p[b0 + by]; - b10 = p[b1 + by]; - - by++; - - b01 = p[b0 + by]; - b11 = p[b1 + by]; - - sx = s_curve(rx0); - sy = s_curve(ry0); - sz = s_curve(rz); - - q = G(b00 + bz); - u = rx0 * q[0] + ry0 * q[1] + rz * q[2]; - q = G(b10 + bz); - v = rx1 * q[0] + ry0 * q[1] + rz * q[2]; - a = lerp(sx, u, v); - q = G(b01 + bz); - u = rx0 * q[0] + ry1 * q[1] + rz * q[2]; - q = G(b11 + bz); - v = rx1 * q[0] + ry1 * q[1] + rz * q[2]; - b = lerp(sx, u, v); - c = lerp(sy, a, b); - - bz++; - rz--; - - q = G(b00 + bz); - u = rx0 * q[0] + ry0 * q[1] + rz * q[2]; - q = G(b10 + bz); - v = rx1 * q[0] + ry0 * q[1] + rz * q[2]; - a = lerp(sx, u, v); - q = G(b01 + bz); - u = rx0 * q[0] + ry1 * q[1] + rz * q[2]; - q = G(b11 + bz); - v = rx1 * q[0] + ry1 * q[1] + rz * q[2]; - b = lerp(sx, u, v); - d = lerp(sy, a, b); - - return lerp(sz, c, d); + public void setOctaves( double pOctaves ) { + this.octaves = pOctaves; } - public double noise( double x, double y ) { - int bx0, bx1, by0, by1, b00, b10, b01, b11; - double rx0, rx1, ry0, ry1, sx, sy, a, b, t, u, v, q[]; - int i, j; + public double getPersistence() { + return persistence; + } - t = x + N; - bx0 = ((int) t) & M; - bx1 = (bx0 + 1) & M; - rx0 = t - (int) t; - rx1 = rx0 - 1; + public void setPersistence( double pPersistence ) { + this.persistence = pPersistence; + } - t = y + N; - by0 = ((int) t) & M; - by1 = (by0 + 1) & M; - ry0 = t - (int) t; - ry1 = ry0 - 1; + public double getFrequency() { + return frequency; + } - i = p[bx0]; - j = p[bx1]; + public void setFrequency( double pFrequency ) { + this.frequency = pFrequency; + } - b00 = p[i + by0]; - b10 = p[j + by0]; - b01 = p[i + by1]; - b11 = p[j + by1]; + public double getAmplitude() { + return amplitude; + } - sx = s_curve(rx0); - sy = s_curve(ry0); + public void setAmplitude( double pAmplitude ) { + this.amplitude = pAmplitude; + } - 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); + public void setRange( double pRangeMin, double pRangeMax ) { + this.rangeMin = pRangeMin; + this.rangeMax = pRangeMax; + } - 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); + public double getRangeMin() { + return rangeMin; + } - return lerp(sy, a, b); + public double getRangeMax() { + return rangeMax; + } + + public int getRepeat() { + return repeat; + } + + public void setRepeat( int pRepeat ) { + this.repeat = pRepeat; } public double noise( double x ) { + double total = 0; + double freq = this.frequency; + double amp = this.amplitude; + double maxValue = 0; - int bx0, bx1; - double rx0, rx1, sx, t, u, v; - t = x + N; - bx0 = ((int) t) & M; - bx1 = (bx0 + 1) & M; - rx0 = t - (int) t; - rx1 = rx0 - 1; + for( int i = 0; i < octaves; i++ ) { + total += perlin(x * freq) * amp; - sx = s_curve(rx0); - u = rx0 * g1[p[bx0]]; - v = rx1 * g1[p[bx1]]; + maxValue += amp; - return lerp(sx, u, v); + amp *= persistence; + freq *= 2; + } + + return lerp(rangeMin, rangeMax, (total / maxValue)); } - // ======================================================================== - // PRIVATE - // ======================================================================== - private void normalize2( double v[] ) { - double s; - s = Math.sqrt(v[0] * v[0] + v[1] * v[1]); - v[0] = v[0] / s; - v[1] = v[1] / s; + public double noise( double x, double y ) { + double total = 0; + double freq = this.frequency; + double amp = this.amplitude; + double maxValue = 0; + + for( int i = 0; i < octaves; i++ ) { + total += perlin(x * freq, y * freq) * amp; + + maxValue += amp; + + amp *= persistence; + freq *= 2; + } + + return lerp(rangeMin, rangeMax, (total / maxValue)); } - private double[] G( int i ) { - return points[i % 32]; + public double noise( double x, double y, double z ) { + double total = 0; + double freq = this.frequency; + double amp = this.amplitude; + double maxValue = 0; + + for( int i = 0; i < octaves; i++ ) { + total += perlin(x * freq, y * freq, z * freq) * amp; + + maxValue += amp; + + amp *= persistence; + freq *= 2; + } + + return lerp(rangeMin, rangeMax, (total / maxValue)); } - private double s_curve( double t ) { - return t * t * (3 - t - t); + private double perlin( double x ) { + // @formatter:off + if( repeat > 0 ) { + x %= repeat; + } + + int xi = (int)x & M; + + double xf = x - (int)x; + + double u = fade(xf); + + int a, b; + a = p[ xi ]; + b = p[inc(xi)]; + + return (lerp(grad(a,xf), grad(b,xf-1), u) + 1) / 2; + // @formatter:on } - private double lerp( double t, double a, double b ) { + private double perlin( double x, double y ) { + // @formatter:off + if( repeat > 0 ) { + x %= repeat; + y %= repeat; + } + + int xi = (int) x & M; + int yi = (int) y & M; + + double xf = x - (int) x; + double yf = y - (int) y; + + double u = fade(xf); + double v = fade(yf); + + int aa, ab, ba, bb; + aa = p[p[ xi ] + yi ]; + ab = p[p[ xi ] + inc(yi)]; + ba = p[p[inc(xi)] + yi ]; + bb = p[p[inc(xi)] + inc(yi)]; + + double x1, x2; + x1 = lerp( + grad(aa, xf , yf), + grad(ba, xf-1, yf), + u); + x2 = lerp( + grad(ab, xf , yf-1), + grad(bb, xf-1, yf-1), + u); + + return (lerp(x1, x2, v) + 1) / 2; + // @formatter:on + } + + private double perlin( double x, double y, double z ) { + // @formatter:off + if( repeat > 0 ) { + x %= repeat; + y %= repeat; + z %= repeat; + } + + int xi = (int)x & M; + int yi = (int)y & M; + int zi = (int)z & M; + double xf = x - (int)x; + double yf = y - (int)y; + double zf = z - (int)z; + + double u = fade(xf); + double v = fade(yf); + double w = fade(zf); + + int aaa, aba, aab, abb, baa, bba, bab, bbb; + aaa = p[p[p[ xi ] + yi ] + zi ]; + aba = p[p[p[ xi ] + inc(yi)] + zi ]; + aab = p[p[p[ xi ] + yi ] + inc(zi)]; + abb = p[p[p[ xi ] + inc(yi)] + inc(zi)]; + baa = p[p[p[inc(xi)] + yi ] + zi ]; + bba = p[p[p[inc(xi)] + inc(yi)] + zi ]; + bab = p[p[p[inc(xi)] + yi ] + inc(zi)]; + bbb = p[p[p[inc(xi)] + inc(yi)] + inc(zi)]; + + double x1, x2, y1, y2; + x1 = lerp( + grad(aaa, xf , yf, zf), + grad(baa, xf-1, yf, zf), + u); + x2 = lerp( + grad(aba, xf , yf-1, zf), + grad(bba, xf-1, yf-1, zf), + u); + y1 = lerp(x1, x2, v); + + x1 = lerp( + grad(aab, xf , yf, zf-1), + grad(bab, xf-1, yf, zf-1), + u); + x2 = lerp( + grad(abb, xf , yf-1, zf-1), + grad(bbb, xf-1, yf-1, zf-1), + u); + y2 = lerp(x1, x2, v); + + return (lerp(y1, y2, w) + 1) / 2; + // @formatter:on + } + + public void init( Random rand ) { + p = new int[N * 2]; + + if( rand == null ) { + System.arraycopy(PERLIN_PERMUTATION, 0, p, 0, N); + } else { + // Generate random permutation + for( int i = 0; i < N; i++ ) { + int n = rand.nextInt(N); + if( p[n] == 0 ) + p[n] = i; + else + i--; + } + } + + // Duplicate permutation array to prevent overflow errors + System.arraycopy(p, 0, p, N, N); + } + + private double fade( double t ) { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + private double lerp( double a, double b, double t ) { return a + t * (b - a); } - private final void init() { - int i, j, k; - double u, v, w, U, V, W, Hi, Lo; + private int inc( int i ) { + ++i; + if( repeat > 0 ) + i = i % repeat; + return i; + } - for( i = 0; i < B; i++ ) { - p[i] = i; - g1[i] = 2 * rand.nextDouble() - 1; - - do { - u = 2 * rand.nextDouble() - 1; - v = 2 * rand.nextDouble() - 1; - } while( u * u + v * v > 1 || - Math.abs(u) > 2.5 * Math.abs(v) || - Math.abs(v) > 2.5 * Math.abs(u) || - Math.abs(Math.abs(u) - Math.abs(v)) < .4 ); - - g2[i][0] = u; - g2[i][1] = v; - - normalize2(g2[i]); - - do { - u = 2 * rand.nextDouble() - 1; - v = 2 * rand.nextDouble() - 1; - w = 2 * rand.nextDouble() - 1; - U = Math.abs(u); - V = Math.abs(v); - W = Math.abs(w); - Lo = Math.min(U, Math.min(V, W)); - Hi = Math.max(U, Math.max(V, W)); - } while( u * u + v * v + w * w > 1 - || Hi > 4 * Lo - || Math.min(Math.abs(U - V), - Math.min(Math.abs(U - W), Math.abs(V - W))) < .2 ); - } - - while( --i > 0 ) { - k = p[i]; - j = (int) (rand.nextLong() & M); - 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]; - } - - points[3][0] = points[3][1] = points[3][2] = Math.sqrt(1. / 3); - double r2 = Math.sqrt(1. / 2); - double s = Math.sqrt(2 + r2 + r2); - - for( i = 0; i < 3; i++ ) - for( j = 0; j < 3; j++ ) - points[i][j] = (i == j ? 1 + r2 + r2 : r2) / s; - - - for( i = 0; i <= 1; i++ ) { - for( j = 0; j <= 1; j++ ) { - for( k = 0; k <= 1; k++ ) { - int n = i + j * 2 + k * 4; - if( n > 0 ) { - for( int m = 0; m < 4; m++ ) { - points[4 * n + m][0] = (i == 0 ? 1 : -1) * points[m][0]; - points[4 * n + m][1] = (j == 0 ? 1 : -1) * points[m][1]; - points[4 * n + m][2] = (k == 0 ? 1 : -1) * points[m][2]; - } - } - } - } + private double grad( int hash, double x ) { + switch( hash & 0x1 ) { + // @formatter:off + case 0x0: return x; + case 0x1: return -x; + default: return 0; + // @formatter:on } } + + private double grad( int hash, double x, double y ) { + switch( hash & 0x3 ) { + // @formatter:off + case 0x0: return x; + case 0x1: return -x; + case 0x2: return y; + case 0x3: return -y; + default: return 0; + // @formatter:on + } + } + + private double grad( int hash, double x, double y, double z ) { + switch( hash & 0xF ) { + // @formatter:off + case 0x0: return x + y; + case 0x1: return -x + y; + case 0x2: return x - y; + case 0x3: return -x - y; + case 0x4: return x + z; + case 0x5: return -x + z; + case 0x6: return x - z; + case 0x7: return -x - z; + case 0x8: return y + z; + case 0x9: return -y + z; + case 0xA: return y - z; + case 0xB: return -y - z; + case 0xC: return y + x; + case 0xD: return -y + z; + case 0xE: return y - x; + case 0xF: return -y - z; + default: return 0; // never happens + // @formatter:on + } + } + + private static final int[] PERLIN_PERMUTATION = new int[]{ + 151, 160, 137, 91, 90, 15, + 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, + 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, + 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, + 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, + 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, + 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, + 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, + 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, + 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, + 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, + 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, + 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 + }; + } diff --git a/src/test/java/schule/ngb/zm/util/NoiseTest.java b/src/test/java/schule/ngb/zm/util/NoiseTest.java new file mode 100644 index 0000000..5488b57 --- /dev/null +++ b/src/test/java/schule/ngb/zm/util/NoiseTest.java @@ -0,0 +1,4 @@ +import static org.junit.jupiter.api.Assertions.*; +class NoiseTest { + +}